Compare commits

...

92 Commits

Author SHA1 Message Date
Julian Lam
d4d4c3cc92 0.0.6 2013-08-26 17:13:14 -04:00
Julian Lam
3a12ba177a possibly fixing issue #202 again 2013-08-26 17:10:41 -04:00
Julian Lam
28e1538fdb issue #203 2013-08-26 16:54:15 -04:00
Julian Lam
eed66c099b fixing double notif error on new notif 2013-08-26 16:42:29 -04:00
Julian Lam
ece2edf579 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-26 16:37:37 -04:00
Julian Lam
1961f01cab possibly fixing issue #203 2013-08-26 16:37:22 -04:00
Baris Usakli
4a214b6ef0 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-26 16:04:37 -04:00
Baris Usakli
dda429ab5d unread count shows total 2013-08-26 16:04:31 -04:00
Julian Lam
15feaafd68 attempting fix for #179, @barisusakli can you pull and test if this is still present? 2013-08-26 15:20:06 -04:00
Julian Lam
14e7907e06 closed #201 2013-08-26 14:56:00 -04:00
Baris Usakli
b4b483b35a removed console.log 2013-08-26 13:21:14 -04:00
Baris Usakli
79c9bdba7f removed console.log 2013-08-26 13:20:10 -04:00
Baris Usakli
a6837a7869 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-26 13:18:29 -04:00
Baris Usakli
0a485a7ff6 closes #109 2013-08-26 13:18:20 -04:00
Julian Lam
bca1602474 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-26 12:24:16 -04:00
Julian Lam
96ee0a2017 closed #184 2013-08-26 12:22:55 -04:00
Baris Usakli
25550e18d0 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-26 11:59:02 -04:00
Baris Usakli
154d0160bc closes #200 2013-08-26 11:58:52 -04:00
Julian Lam
aefa56221b closed #202 2013-08-26 11:56:28 -04:00
Baris Soner Usakli
dd40cbc139 check if topicData is valid 2013-08-24 18:51:24 -04:00
Baris Soner Usakli
23db2e5c9e better fix for #198 2013-08-24 18:47:10 -04:00
Baris Soner Usakli
eff1b174c0 closes #198 2013-08-24 18:43:06 -04:00
Baris Soner Usakli
545069b069 closes #199 2013-08-24 18:19:01 -04:00
Julian Lam
f767535ce5 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-23 16:25:22 -04:00
Julian Lam
aeebd069e2 fixing nconf issue where redis js was requiring redis configs before they were defined 2013-08-23 16:25:16 -04:00
Julian Lam
d2aa2a9a29 derpiness 2013-08-23 15:22:26 -04:00
Julian Lam
d7eb30ccbd fuck git pt 2 2013-08-23 14:57:33 -04:00
Julian Lam
9bc12f28b4 fuck git 2013-08-23 14:55:25 -04:00
Julian Lam
4d11fba20a auto-focusing input on login screen 2013-08-23 14:30:58 -04:00
Baris Usakli
cb6f587f24 closes #197 2013-08-23 13:45:57 -04:00
Baris Usakli
c647793512 meta config changes, refactors 2013-08-23 13:14:36 -04:00
Julian Lam
caa057ff4d Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-22 14:42:13 -04:00
Julian Lam
43152bdaf9 closed #194 - refactoring of post creation so that, normalizing post
objects

also fixes julianlam/nodebb-plugin-mentions#1
2013-08-22 14:40:28 -04:00
Baris Soner Usakli
4cdb7ff32b closes #192 2013-08-22 13:47:07 -04:00
Julian Lam
7cbb01a78a making RDB available to plugins without require 2013-08-22 11:42:49 -04:00
Julian Lam
555eddff83 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-22 10:48:28 -04:00
Julian Lam
a3cab53b73 added username mentions plugin to default, and tweaked admin panel to show
plugins installed via npm
2013-08-22 10:47:24 -04:00
Baris Soner Usakli
454d5827fd overflow hidden on users recent posts 2013-08-22 10:18:17 -04:00
Baris Soner Usakli
186c426691 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-22 10:14:15 -04:00
Baris Soner Usakli
5e1e1ecf6f moved refreshTitle into load_template callback 2013-08-22 10:14:07 -04:00
Julian Lam
2d3d0f688a a couple minor 'tweaks' to the plugin system so that it works with npm installed plugins 2013-08-22 09:50:29 -04:00
Julian Lam
7975844e4d renamed two hooks and added filter:post.get hook 2013-08-21 22:40:47 -04:00
Baris Soner Usakli
f2b138510e removed console.log 2013-08-21 19:58:45 -04:00
Baris Soner Usakli
1c95ef4060 fix for infinite scroll crashing, issue #187 2013-08-21 19:57:11 -04:00
Baris Soner Usakli
b43e12a42a closes #187, closes #190 2013-08-21 19:21:49 -04:00
Baris Soner Usakli
3f793ae902 changed document.body.offsetHeight to .height() 2013-08-21 14:33:05 -04:00
Baris Soner Usakli
ac56f3a30a change document.body.scrollTop to .scrollTop() 2013-08-21 13:17:16 -04:00
psychobunny
e2ffac74bc added css classes to hide/show elements based on logged in status; hid unread category to anonymous users
added some general classes .nodebb-loggedin and .nodebb-loggedout for
toggling display based on user status
2013-08-22 00:40:37 +08:00
psychobunny
1c08ca54c5 added subtle glowing css animation to unread notification icon 2013-08-22 00:22:05 +08:00
psychobunny
f1547a7b1f added badge to header for unread topics count 2013-08-22 00:08:11 +08:00
psychobunny
746fa93c80 added API call for total unread topics; moved unread notification parsing to client side
also fixed a bug where the new notification icon glow would disappear on
page refresh even if there were existing notifications.
2013-08-21 23:34:35 +08:00
psychobunny
5ab1758d28 simple toaster popup on new notification 2013-08-21 23:08:51 +08:00
Julian Lam
2e4e1df3d9 closed #186 - infinite scroller not working in firefox 2013-08-20 22:36:46 -04:00
psychobunny
eded61d66e typo 2013-08-21 03:40:23 +08:00
psychobunny
d9360da9a5 default motd: removed version number and button text on mobile layouts 2013-08-21 03:37:48 +08:00
Baris Usakli
2b7a1b7515 changed the recursion to load posts to a while loop 2013-08-20 12:31:08 -04:00
Baris Usakli
1e66116e1d closes #181 2013-08-20 12:11:17 -04:00
Baris Soner Usakli
a95582b382 closes #183, closes #182 2013-08-20 00:50:59 -04:00
Baris Soner Usakli
7860cfdec3 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-20 00:26:58 -04:00
Baris Soner Usakli
30bbea9c74 closes #175 2013-08-20 00:26:50 -04:00
Baris Usakli
481105d6be reverted that back, it would take other users to the topic too 2013-08-19 14:54:11 -04:00
Baris Usakli
f939a632a6 take to topic after creation 2013-08-19 14:43:37 -04:00
Baris Usakli
c05f56d28c fix for category view when its empty 2013-08-19 14:28:51 -04:00
Baris Usakli
b844251587 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-19 14:03:31 -04:00
Baris Usakli
b9bd907a6b speech bubbles 2013-08-19 14:03:20 -04:00
psychobunny
17d86a2a35 force vertical scrollbar to prevent centering jumps during navigation 2013-08-20 01:50:19 +08:00
Baris Usakli
c70c67394a closes #171 2013-08-19 13:31:04 -04:00
Baris Usakli
92d3559146 closes #176 2013-08-19 11:56:40 -04:00
Baris Usakli
0e9a3c3a9f hide load more button if not topics are left 2013-08-19 11:02:31 -04:00
Baris Usakli
41263f0332 closes #178 2013-08-19 10:58:02 -04:00
Julian Lam
3747427538 updated readme to remove indiegogo info, and to add 0.0.5 screenshots 2013-08-19 10:13:18 -04:00
Baris Soner Usakli
b65554ca15 removed console.log 2013-08-16 17:04:41 -04:00
Baris Soner Usakli
00cb15d3c8 check if there is follow element 2013-08-16 17:04:08 -04:00
Baris Soner Usakli
6690f49c4e added profile views to users, little cleanup to use app.addCommasToNumbers 2013-08-16 13:03:56 -04:00
Baris Soner Usakli
ff805a704d closes #175 2013-08-15 17:03:43 -04:00
Baris Soner Usakli
f83be710a0 closes #172, #173, #174 2013-08-15 16:50:00 -04:00
Julian Lam
3933549659 committing fixes to composer window 2013-08-15 14:04:12 -04:00
Julian Lam
4993b74c23 Merge remote-tracking branch 'origin' 2013-08-15 11:40:53 -04:00
Julian Lam
76e7a98c88 style fixes to image-drop list in composer window 2013-08-15 11:40:41 -04:00
Baris Soner Usakli
999e98e43d add label to banned users, dont hide their posts 2013-08-14 17:42:36 -04:00
Baris Usakli
74af205426 banned users cant login, show error messages on failed logins 2013-08-14 15:49:56 -04:00
Baris Usakli
9ad82f4907 dont filter banned when searching if user is admin 2013-08-14 14:26:09 -04:00
Baris Usakli
2e6b37e018 wrapped search form in li 2013-08-14 13:56:06 -04:00
Baris Usakli
67070e335e removed console.log 2013-08-14 13:44:18 -04:00
Baris Usakli
22536e694c fixes wrong topics getting loaded into wrong categories 2013-08-14 13:43:42 -04:00
Baris Usakli
929282a2f7 ban users, closes #125, banning a user hides all posts topics of a user and their profile page becomes inaccessible 2013-08-14 13:32:07 -04:00
Baris Soner Usakli
b0092b68c6 closes #166 2013-08-13 17:29:16 -04:00
Baris Usakli
91446378bd require winston 2013-08-13 16:02:21 -04:00
Baris Usakli
dceec0ce46 more winston, issue #62 2013-08-13 16:00:24 -04:00
Baris Usakli
1856e394f3 more winston 2013-08-13 15:25:40 -04:00
Baris Usakli
8dc7a0dbbf changed favouriting to wait for socket call to end before changing star class 2013-08-13 15:05:35 -04:00
Baris Usakli
6e17ff7981 added winston, added wrapper for winston.error until they fix it, issue #62 2013-08-13 14:45:28 -04:00
65 changed files with 1870 additions and 1168 deletions

View File

@@ -1,16 +1,15 @@
Please support NodeBB development! Check out our IndieGoGo campaign and like, share, and follow us :) Please support NodeBB development! Check out our IndieGoGo campaign and like, share, and follow us :)
[NodeBB Homepage](http://www.nodebb.org/ "NodeBB") # [IndieGoGo campaign](https://www.indiegogo.com/projects/nodebb-the-discussion-platform-of-the-future/ "IndieGoGo") # [Follow on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") # [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook") [NodeBB Homepage](http://www.nodebb.org/ "NodeBB") # [Follow on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") # [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
# NodeBB # NodeBB
**NodeBB** is a robust Node.js driven forum built on a redis database. It is powered by web sockets, and is compatible down to IE8. **NodeBB** is a robust Node.js driven forum built on a redis database. It is powered by web sockets, and is compatible down to IE8.
![NodeBB Main Category Listing](http://i.imgur.com/N8SLkcDh.png) ![NodeBB Main Category Listing](http://imgur.com/BdImzGs.png)
![NodeBB Topic Page](http://i.imgur.com/RBkFzdkh.png) ![NodeBB Topic Page](http://imgur.com/Lk7E3KR.png)
## How can I follow along/contribute? ## How can I follow along/contribute?
* Our [Indiegogo campaign](https://www.indiegogo.com/projects/nodebb-the-discussion-platform-of-the-future/) is accepting contributions until August 17th, 2013
* Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/designcreateplay/NodeBB/wiki/Version-History-%26-Roadmap) * Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/designcreateplay/NodeBB/wiki/Version-History-%26-Roadmap)
* If you are a developer, feel free to check out the source and submit pull requests. * If you are a developer, feel free to check out the source and submit pull requests.
* If you are a designer, NodeBB needs themes! NodeBB will accept any LESS or CSS file and use it in place of the default Twitter Bootstrap theme. Consider extending Bootstrap themes by extending the base bootstrap LESS file. * If you are a designer, NodeBB needs themes! NodeBB will accept any LESS or CSS file and use it in place of the default Twitter Bootstrap theme. Consider extending Bootstrap themes by extending the base bootstrap LESS file.

84
app.js
View File

@@ -16,45 +16,64 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// Configuration setup
nconf = require('nconf');
nconf.argv().file({ file: __dirname + '/config.json'});
var fs = require('fs'), var fs = require('fs'),
nconf = require('nconf'), winston = require('winston'),
pkg = require('./package.json'), pkg = require('./package.json'),
url = require('url'); url = require('url'),
meta = require('./src/meta.js');
// Runtime environment // Runtime environment
global.env = process.env.NODE_ENV || 'production', global.env = process.env.NODE_ENV || 'production',
// Configuration setup
nconf.argv().file({ file: __dirname + '/config.json'});
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
colorize:true
});
winston.add(winston.transports.File, {
filename:'error.log',
level:'error'
})
// TODO: remove once https://github.com/flatiron/winston/issues/280 is fixed
winston.err = function(err) {
winston.error(err.stack);
};
// Log GNU copyright info along with server info // Log GNU copyright info along with server info
console.log('Info: NodeBB v' + pkg.version + ' Copyright (C) 2013 DesignCreatePlay Inc.'); winston.info('NodeBB v' + pkg.version + ' Copyright (C) 2013 DesignCreatePlay Inc.');
console.log('Info: This program comes with ABSOLUTELY NO WARRANTY.'); winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
console.log('Info: This is free software, and you are welcome to redistribute it under certain conditions.'); winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
console.log('Info: ==='); winston.info('===');
if(nconf.get('upgrade')) { if(nconf.get('upgrade')) {
require('./src/upgrade').upgrade(); meta.configs.init(function() {
require('./src/upgrade').upgrade();
});
} else if (!nconf.get('setup') && nconf.get('base_url')) { } else if (!nconf.get('setup') && nconf.get('base_url')) {
nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path') + '/'); 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('upload_url', nconf.get('url') + 'uploads/');
global.nconf = nconf;
console.log('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('Initializing NodeBB v' + pkg.version + ', on port ' + nconf.get('port') + ', using Redis store at ' + nconf.get('redis:host') + ':' + nconf.get('redis:port') + '.');
console.log('Info: Base Configuration OK.'); winston.info('Base Configuration OK.');
// TODO: Replace this with nconf-redis meta.configs.init(function() {
var meta = require('./src/meta.js'); // Initial setup for Redis & Reds
global.config = {}; var reds = require('reds');
meta.config.get(function(config) { RDB = require('./src/redis.js');
for(c in config) { reds.createClient = function() {
if (config.hasOwnProperty(c)) { return reds.client || (reds.client = RDB);
global.config[c] = config[c];
}
} }
var categories = require('./src/categories.js'), var categories = require('./src/categories.js'),
RDB = require('./src/redis.js'),
templates = require('./public/src/templates.js'), templates = require('./public/src/templates.js'),
webserver = require('./src/webserver.js'), webserver = require('./src/webserver.js'),
websockets = require('./src/websockets.js'), websockets = require('./src/websockets.js'),
@@ -74,7 +93,7 @@ if(nconf.get('upgrade')) {
templates.init([ templates.init([
'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index', 'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index',
'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext', 'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext',
'emails/header', 'emails/footer', 'install/header', 'install/footer', 'install/redis', 'emails/header', 'emails/footer',
'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic' 'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic'
]); ]);
@@ -83,10 +102,10 @@ if(nconf.get('upgrade')) {
//setup scripts to be moved outside of the app in future. //setup scripts to be moved outside of the app in future.
function setup_categories() { function setup_categories() {
console.log('Info: Checking categories...'); winston.info('Checking categories...');
categories.getAllCategories(function(data) { categories.getAllCategories(function(data) {
if (data.categories.length === 0) { if (data.categories.length === 0) {
console.log('Info: Setting up default categories...'); winston.info('Setting up default categories...');
fs.readFile(config.ROOT_DIRECTORY + '/install/data/categories.json', function(err, default_categories) { fs.readFile(config.ROOT_DIRECTORY + '/install/data/categories.json', function(err, default_categories) {
default_categories = JSON.parse(default_categories); default_categories = JSON.parse(default_categories);
@@ -96,27 +115,26 @@ if(nconf.get('upgrade')) {
} }
}); });
console.log('Info: Hardcoding uid 1 as an admin'); winston.info('Hardcoding uid 1 as an admin');
var user = require('./src/user.js'); var user = require('./src/user.js');
user.makeAdministrator(1); user.makeAdministrator(1);
} else { } else {
console.log('Info: Categories OK. Found ' + data.categories.length + ' categories.'); winston.info('Categories OK. Found ' + data.categories.length + ' categories.');
} }
}); });
} }
setup_categories(); setup_categories();
}(global.configuration)); }(global.configuration));
}); });
} else { } else {
// New install, ask setup questions // New install, ask setup questions
if (nconf.get('setup')) console.log('Info: NodeBB Setup Triggered via Command Line'); if (nconf.get('setup')) winston.info('NodeBB Setup Triggered via Command Line');
else console.log('Info: Configuration not found, starting NodeBB setup'); else winston.info('Configuration not found, starting NodeBB setup');
var install = require('./src/install'); var install = require('./src/install');
@@ -128,7 +146,7 @@ if(nconf.get('upgrade')) {
install.setup(function(err) { install.setup(function(err) {
if (err) { if (err) {
console.log('Error: There was a problem completing NodeBB setup: ', err.message); winston.error('There was a problem completing NodeBB setup: ', err.message);
} else { } else {
if (!nconf.get('setup')) { if (!nconf.get('setup')) {
process.stdout.write( process.stdout.write(
@@ -136,7 +154,7 @@ if(nconf.get('upgrade')) {
); );
} }
} }
process.exit(); process.exit();
}); });
} }

View File

@@ -2,7 +2,7 @@
"name": "nodebb", "name": "nodebb",
"license": "GPLv3 or later", "license": "GPLv3 or later",
"description": "NodeBB Forum", "description": "NodeBB Forum",
"version": "0.0.5", "version": "0.0.6",
"homepage": "http://www.nodebb.org", "homepage": "http://www.nodebb.org",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -33,7 +33,9 @@
"sitemap": "~0.6.0", "sitemap": "~0.6.0",
"cheerio": "~0.12.0", "cheerio": "~0.12.0",
"request": "~2.25.0", "request": "~2.25.0",
"reds": "~0.2.4" "reds": "~0.2.4",
"winston": "~0.7.2",
"nodebb-plugin-mentions": "~0.1.0"
}, },
"bugs": { "bugs": {
"url": "https://github.com/designcreateplay/NodeBB/issues" "url": "https://github.com/designcreateplay/NodeBB/issues"

View File

@@ -1,5 +1,9 @@
@import "mixins"; @import "mixins";
html {
overflow-y: scroll;
}
body { body {
/*background: #fdfdfd;*/ // port to default theme when it is implemented. /*background: #fdfdfd;*/ // port to default theme when it is implemented.
-webkit-transition: margin-bottom 250ms ease; -webkit-transition: margin-bottom 250ms ease;
@@ -38,7 +42,7 @@ button, a {
vertical-align: 17%; vertical-align: 17%;
} }
.nav .badge { .nav .badge {
vertical-align: 10%; vertical-align: 2%;
} }
#alert_window { #alert_window {
@@ -197,6 +201,12 @@ footer.footer {
font-weight:bold; font-weight:bold;
} }
.account-block {
div {
padding-bottom:10px;
}
}
.account-picture-block{ .account-picture-block{
display:inline-block; display:inline-block;
vertical-align:top; vertical-align:top;
@@ -213,7 +223,6 @@ footer.footer {
.user-profile-picture { .user-profile-picture {
width:128px; width:128px;
margin-bottom:10px;
} }
.user-picture-label { .user-picture-label {
@@ -235,6 +244,7 @@ footer.footer {
color: #333; color: #333;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer; cursor: pointer;
overflow:hidden;
p { p {
color: #333; color: #333;
} }
@@ -435,14 +445,33 @@ body .navbar .nodebb-inline-block {
padding-bottom:20px; padding-bottom:20px;
} }
.dropdown-toggle { .dropdown-toggle {
i { i {
font-size: 12px; font-size: 12px;
@-webkit-keyframes glow
{
from {text-shadow: 0 0 5px #aaf, 0 0 5px #aaf, 0 0 5px #aaf;}
50% {text-shadow: 0 0 10px #aaf, 0 0 10px #aaf, 0 0 10px #aaf;}
to {text-shadow: 0 0 5px #aaf, 0 0 5px #aaf, 0 0 5px #aaf;}
}
@keyframes glow
{
from {text-shadow: 0 0 5px #aaf, 0 0 5px #aaf, 0 0 5px #aaf;}
50% {text-shadow: 0 0 10px #aaf, 0 0 10px #aaf, 0 0 10px #aaf;}
to {text-shadow: 0 0 5px #aaf, 0 0 5px #aaf, 0 0 5px #aaf;}
}
&.active { &.active {
color: #558; color: #558;
text-shadow: 0 0 1em #aaf, 0 0 1em #aaf, 0 0 1em #aaf; text-shadow: 0 0 1em #aaf, 0 0 1em #aaf, 0 0 1em #aaf;
-webkit-animation:glow 1.5s infinite linear;
animation:glow 1.5s infinite linear;
} }
} }
} }
@@ -523,16 +552,23 @@ body .navbar .nodebb-inline-block {
visibility: visible; visibility: visible;
.btn-toolbar { .btn-toolbar {
width: 90%; &.formatting-bar {
margin: 0 auto; width: 90%;
margin: 0 auto 8px auto;
span { span {
color: white; color: white;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
}
} }
} }
&.action-bar {
width: 90%;
margin: 8px auto 0 auto;
}
} }
input { input {
@@ -543,7 +579,8 @@ body .navbar .nodebb-inline-block {
-webkit-border-radius: 0px; -webkit-border-radius: 0px;
-moz-border-radius: 0px; -moz-border-radius: 0px;
border-radius: 0px; border-radius: 0px;
margin: 1% 1% 2% 1%; margin: 5px auto 10px auto;
display: block;
} }
textarea { textarea {
@@ -552,41 +589,43 @@ body .navbar .nodebb-inline-block {
padding: 0.5em; padding: 0.5em;
display: block; display: block;
width: 90%; width: 90%;
margin: 0.5em auto; margin: 0em auto;
resize: none; resize: none;
color: white; color: white;
height: 200px; height: 200px;
} }
#imagedrop { .imagedrop {
text-align:center; text-align: center;
color:white; color: white;
position: absolute; position: absolute;
top: 0px; top: 0px;
left: 0px; left: 0px;
width: 100%; width: 100%;
height:230px; height: 214px;
line-height:230px; line-height: 214px;
font-size:20px; font-size: 20px;
vertical-align: middle; vertical-align: middle;
display: none;
} }
#imagelist { .imagelist {
position: absolute; position: absolute;
bottom: 5px; bottom: 50px;
left: 0px; left: 5%;
padding-left:2em;
div {
div { margin-right:5px;
margin-right:5px; }
}
span { span {
line-height:20px; line-height:20px;
float:left; float:left;
} }
button {
padding-left:5px; button {
} padding-left:5px;
}
} }
} }
} }
@@ -827,7 +866,7 @@ body .navbar .nodebb-inline-block {
.form-search { .form-search {
float: left; float: left;
margin-top: 5px; margin-top: 5px;
margin-bottom:0px; margin-bottom: 5px;
} }
.search-result-post { .search-result-post {

View File

@@ -32,6 +32,7 @@
} }
.profile-block, .post-block { .profile-block, .post-block {
position:relative;
border: 1px solid rgba(0, 0, 0, 0.06); border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 10px;
@@ -98,12 +99,16 @@
} }
} }
&.deleted { &.deleted {
-moz-opacity: 0.30; -moz-opacity: 0.30;
opacity: 0.30; opacity: 0.30;
} }
/*http://stackoverflow.com/questions/11037517/bootstrap-making-responsive-changes-to-layout*/ /*http://stackoverflow.com/questions/11037517/bootstrap-making-responsive-changes-to-layout*/
@media (max-width: 979px) { @media (max-width: 979px) {
.span12-tablet { .span12-tablet {
width: 100% !important; width: 100% !important;
margin-left:0px; margin-left:0px;
@@ -111,14 +116,42 @@
} }
} }
@media (min-width: 979px) {
.speech-bubble:after
{
content: "";
position: absolute;
top: 9px;
left: -7px;
border-style: solid;
border-width: 7px 7px 7px 0;
border-color: transparent #FFFFFF;
display: block;
width: 0;
z-index: 1;
}
.speech-bubble:before
{
content: "";
position: absolute;
top: 9px;
left: -8px;
border-style: solid;
border-width: 7px 7px 7px 0;
border-color: transparent rgba(0, 0, 0, 0.125);
display: block;
width: 0;
z-index: 0;
}
}
.main-post { .main-post {
h3 { h3 {
margin: 0; margin: 0;
.topic-title { .topic-title {
width: auto; width: auto;
white-space: nowrap;
text-overflow:ellipsis;
overflow: hidden; overflow: hidden;
margin: 0; margin: 0;
padding: 0; padding: 0;

View File

@@ -2,7 +2,7 @@ var ajaxify = {};
(function($) { (function($) {
var location = document.location || window.location, var location = document.location || window.location,
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''), rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
content = null; content = null;
@@ -26,6 +26,7 @@ var ajaxify = {};
}; };
ajaxify.go = function(url, callback, template, quiet) { ajaxify.go = function(url, callback, template, quiet) {
$(window).off('scroll');
// leave room and join global // leave room and join global
app.enter_room('global'); app.enter_room('global');
@@ -38,14 +39,14 @@ var ajaxify = {};
} }
var tpl_url = templates.get_custom_map(url.split('?')[0]); var tpl_url = templates.get_custom_map(url.split('?')[0]);
if (tpl_url == false && !templates[url]) { if (tpl_url == false && !templates[url]) {
if(url === '' || url === '/') { if(url === '' || url === '/') {
tpl_url = 'home'; tpl_url = 'home';
} else { } else {
tpl_url = url.split('/')[0].split('?')[0]; tpl_url = url.split('/')[0].split('?')[0];
} }
} else if (templates[url]) { } else if (templates[url]) {
tpl_url = url; tpl_url = url;
} }
@@ -66,9 +67,9 @@ var ajaxify = {};
if (callback) { if (callback) {
callback(); callback();
} }
app.process_page(); app.process_page();
jQuery('#content, #footer').stop(true, true).fadeIn(200, function() { jQuery('#content, #footer').stop(true, true).fadeIn(200, function() {
if(window.location.hash) if(window.location.hash)
hash = window.location.hash; hash = window.location.hash;
@@ -76,11 +77,9 @@ var ajaxify = {};
app.scrollToPost(hash.substr(1)); app.scrollToPost(hash.substr(1));
}); });
}, url, template); utils.refreshTitle(url);
socket.emit('api:meta.buildTitle', url, function(title) { }, url, template);
document.title = title;
});
return true; return true;
} }
@@ -95,12 +94,15 @@ var ajaxify = {};
// Enhancing all anchors to ajaxify... // Enhancing all anchors to ajaxify...
$(document.body).on('click', 'a', function(e) { $(document.body).on('click', 'a', function(e) {
if (this.href == window.location.href + "#") return;
if(this.href.slice(-1) === "#") return; function hrefEmpty(href) {
return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#";
}
if(hrefEmpty(this.href)) return;
var url = this.href.replace(rootUrl +'/', ''); var url = this.href.replace(rootUrl +'/', '');
if (this.target !== '') return; if (this.target !== '') return;
if (!e.ctrlKey && e.which === 1) { if (!e.ctrlKey && e.which === 1) {
@@ -127,7 +129,7 @@ var ajaxify = {};
script.type = "text/javascript"; script.type = "text/javascript";
try { try {
script.appendChild(document.createTextNode(data)); script.appendChild(document.createTextNode(data));
} catch(e) { } catch(e) {
script.text = data; script.text = data;
} }
@@ -163,5 +165,5 @@ var ajaxify = {};
evalScript(scripts[i]); evalScript(scripts[i]);
} }
}; };
}(jQuery)); }(jQuery));

View File

@@ -8,18 +8,18 @@ var socket,
var showWelcomeMessage = false; var showWelcomeMessage = false;
function loadConfig() { function loadConfig() {
$.ajax({ $.ajax({
url: RELATIVE_PATH + '/api/config', url: RELATIVE_PATH + '/api/config',
success: function(data) { success: function(data) {
API_URL = data.api_url; API_URL = data.api_url;
config = data; config = data;
socket = io.connect(config.socket.address + (config.socket.port ? ':' + config.socket.port : '')); socket = io.connect(config.socket.address + (config.socket.port ? ':' + config.socket.port : ''));
var reconnecting = false; var reconnecting = false;
var reconnectTries = 0; var reconnectTries = 0;
socket.on('event:connect', function(data) { socket.on('event:connect', function(data) {
console.log('connected to nodebb socket: ', data); console.log('connected to nodebb socket: ', data);
app.username = data.username; app.username = data.username;
@@ -29,11 +29,7 @@ var socket,
socket.on('event:alert', function(data) { socket.on('event:alert', function(data) {
app.alert(data); app.alert(data);
}); });
socket.on('event:consolelog', function(data) {
console.log(data);
});
socket.on('connect', function(data){ socket.on('connect', function(data){
if(reconnecting) { if(reconnecting) {
setTimeout(function(){ setTimeout(function(){
@@ -50,28 +46,28 @@ var socket,
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] }); socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
} }
}); });
socket.on('reconnecting', function(data) { socket.on('reconnecting', function(data) {
function showDisconnectModal() { function showDisconnectModal() {
$('#disconnect-modal').modal({ $('#disconnect-modal').modal({
backdrop:'static', backdrop:'static',
show:true show:true
}); });
$('#reload-button').on('click',function(){ $('#reload-button').on('click',function(){
$('#disconnect-modal').modal('hide'); $('#disconnect-modal').modal('hide');
window.location.reload(); window.location.reload();
}); });
} }
reconnecting = true; reconnecting = true;
reconnectTries++; reconnectTries++;
if(reconnectTries > 4) { if(reconnectTries > 4) {
showDisconnectModal(); showDisconnectModal();
return; return;
} }
app.alert({ app.alert({
alert_id: 'connection_alert', alert_id: 'connection_alert',
title: 'Reconnecting', title: 'Reconnecting',
@@ -80,15 +76,15 @@ var socket,
timeout: 5000 timeout: 5000
}); });
}); });
socket.on('api:user.get_online_users', function(users) { socket.on('api:user.get_online_users', function(users) {
jQuery('.username-field').each(function() { jQuery('.username-field').each(function() {
if (this.processed === true) if (this.processed === true)
return; return;
var el = jQuery(this), var el = jQuery(this),
uid = el.parents('li').attr('data-uid'); uid = el.parents('li').attr('data-uid');
if (uid && jQuery.inArray(uid, users) !== -1) { if (uid && jQuery.inArray(uid, users) !== -1) {
el.find('i').remove(); el.find('i').remove();
el.prepend('<i class="icon-circle"></i>'); el.prepend('<i class="icon-circle"></i>');
@@ -96,19 +92,19 @@ var socket,
el.find('i').remove(); el.find('i').remove();
el.prepend('<i class="icon-circle-blank"></i>'); el.prepend('<i class="icon-circle-blank"></i>');
} }
el.processed = true; el.processed = true;
}); });
}); });
app.enter_room('global'); app.enter_room('global');
}, },
async: false async: false
}); });
} }
// takes a string like 1000 and returns 1,000 // takes a string like 1000 and returns 1,000
app.addCommas = function(text) { app.addCommas = function(text) {
return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"); return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
@@ -125,14 +121,14 @@ var socket,
}); });
} }
// use unique alert_id to have multiple alerts visible at a time, use the same alert_id to fade out the current instance // use unique alert_id to have multiple alerts visible at a time, use the same alert_id to fade out the current instance
// type : error, success, info, warning/notify // type : error, success, info, warning/notify
// title = bolded title text // title = bolded title text
// message = alert message content // message = alert message content
// timeout default = permanent // timeout default = permanent
// location : alert_window (default) or content // location : alert_window (default) or content
app.alert = function(params) { app.alert = function(params) {
var alert_id = 'alert_button_' + ((params.alert_id) ? params.alert_id : new Date().getTime()); var alert_id = 'alert_button_' + ((params.alert_id) ? params.alert_id : new Date().getTime());
var alert = $('#'+alert_id); var alert = $('#'+alert_id);
@@ -142,7 +138,7 @@ var socket,
$(this).remove(); $(this).remove();
}); });
}, timeout); }, timeout);
$(div).attr('timeoutId', timeoutId); $(div).attr('timeoutId', timeoutId);
} }
@@ -150,7 +146,7 @@ var socket,
alert.find('strong').html(params.title); alert.find('strong').html(params.title);
alert.find('p').html(params.message); alert.find('p').html(params.message);
alert.attr('class', "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type)); alert.attr('class', "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type));
clearTimeout(alert.attr('timeoutId')); clearTimeout(alert.attr('timeoutId'));
startTimeout(alert, params.timeout); startTimeout(alert, params.timeout);
} }
@@ -164,7 +160,7 @@ var socket,
strong.innerHTML = params.title; strong.innerHTML = params.title;
div.className = "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type); div.className = "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type);
div.setAttribute('id', alert_id); div.setAttribute('id', alert_id);
div.appendChild(button); div.appendChild(button);
div.appendChild(strong); div.appendChild(strong);
@@ -176,7 +172,7 @@ var socket,
div.parentNode.removeChild(div); div.parentNode.removeChild(div);
} }
if (params.location == null) if (params.location == null)
params.location = 'alert_window'; params.location = 'alert_window';
jQuery('#'+params.location).prepend(jQuery(div).fadeIn('100')); jQuery('#'+params.location).prepend(jQuery(div).fadeIn('100'));
@@ -220,18 +216,17 @@ var socket,
}); });
} }
app.current_room = null; app.current_room = null;
app.enter_room = function(room) { app.enter_room = function(room) {
if(socket) { if(socket) {
if (app.current_room === room) if (app.current_room === room)
return; return;
socket.emit('event:enter_room', { socket.emit('event:enter_room', {
'enter': room, 'enter': room,
'leave': app.current_room 'leave': app.current_room
}); });
app.current_room = room; app.current_room = room;
} }
}; };
@@ -242,7 +237,7 @@ var socket,
jQuery('.post-row').each(function() { jQuery('.post-row').each(function() {
uids.push(this.getAttribute('data-uid')); uids.push(this.getAttribute('data-uid'));
}); });
socket.emit('api:user.get_online_users', uids); socket.emit('api:user.get_online_users', uids);
} }
@@ -258,7 +253,7 @@ var socket,
var url = window.location.href, var url = window.location.href,
parts = url.split('/'), parts = url.split('/'),
active = parts[parts.length-1]; active = parts[parts.length-1];
jQuery('#main-nav li').removeClass('active'); jQuery('#main-nav li').removeClass('active');
if(active) { if(active) {
jQuery('#main-nav li a').each(function() { jQuery('#main-nav li a').each(function() {
@@ -273,7 +268,7 @@ var socket,
} }
setTimeout(function() { setTimeout(function() {
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes. window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
}, 100); }, 100);
} }
@@ -286,7 +281,7 @@ var socket,
timeout: 5000 timeout: 5000
}); });
} }
if(showWelcomeMessage) { if(showWelcomeMessage) {
showWelcomeMessage = false; showWelcomeMessage = false;
if(document.readyState !== 'complete') { if(document.readyState !== 'complete') {
@@ -330,36 +325,54 @@ var socket,
if(app.infiniteLoaderActive) if(app.infiniteLoaderActive)
return; return;
app.infiniteLoaderActive = true; app.infiniteLoaderActive = true;
socket.emit('api:topic.loadMore', { socket.emit('api:topic.loadMore', {
tid: tid, tid: tid,
after: document.querySelectorAll('#post-container li[data-pid]').length after: document.querySelectorAll('#post-container li[data-pid]').length
}, function(data) { }, function(data) {
app.infiniteLoaderActive = false; app.infiniteLoaderActive = false;
if(data.posts.length) { if(data.posts.length) {
app.createNewPosts(data); app.createNewPosts(data);
if(callback)
callback();
} }
if(callback)
callback(data.posts);
}); });
} }
app.scrollToPost = function(pid) { app.scrollToPost = function(pid) {
if(!pid) if(!pid)
return; return;
var container = $(document.body),
scrollTo = $('#post_anchor_' + pid);
if(!scrollTo.length) { var container = $(document.body),
var tid = $('#post-container').attr('data-tid'); scrollTo = $('#post_anchor_' + pid),
app.loadMorePosts(tid, function() { tid = $('#post-container').attr('data-tid');
app.scrollToPost(pid);
function animateScroll() {
$('body,html').animate({
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
}); });
return;
} }
container.animate({ if(!scrollTo.length && tid) {
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
}); 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() { jQuery('document').ready(function() {
@@ -428,6 +441,6 @@ var socket,
el.off('mousemove'); el.off('mousemove');
onTouchEnd(); onTouchEnd();
}); });
} }
}()); }());

View File

@@ -5,11 +5,7 @@
$(document).ready(function() { $(document).ready(function() {
var rep = $('#reputation'); app.addCommasToNumbers();
rep.html(app.addCommas(rep.html()));
var postcount = $('#postcount');
postcount.html(app.addCommas(postcount.html()));
var followBtn = $('#follow-btn'); var followBtn = $('#follow-btn');
var unfollowBtn = $('#unfollow-btn'); var unfollowBtn = $('#unfollow-btn');

View File

@@ -2,19 +2,35 @@
var yourid = templates.get('yourid'), var yourid = templates.get('yourid'),
theirid = templates.get('theirid'); theirid = templates.get('theirid');
function createMenu() {
var userslug = $('.account-username-box').attr('data-userslug');
var links = $('<div class="account-sub-links inline-block pull-right">\
<span id="settingsLink" class="pull-right"><a href="/users/' + userslug + '/settings">settings</a></span>\
<span id="favouritesLink" class="pull-right"><a href="/users/' + userslug + '/favourites">favourites</a></span>\
<span class="pull-right"><a href="/users/' + userslug + '/followers">followers</a></span>\
<span class="pull-right"><a href="/users/' + userslug + '/following">following</a></span>\
<span id="editLink" class="pull-right"><a href="/users/' + userslug + '/edit">edit</a></span>\
</div>');
$('.account-username-box').append(links);
}
$(document).ready(function() { $(document).ready(function() {
createMenu();
var editLink = $('#editLink'); var editLink = $('#editLink');
var settingsLink = $('#settingsLink'); var settingsLink = $('#settingsLink');
var favouritesLink = $('#favouritesLink');
if(yourid === "0") { if(yourid === "0" || yourid !== theirid) {
editLink.hide();
settingsLink.hide();
}
else if(yourid !== theirid) {
editLink.hide(); editLink.hide();
settingsLink.hide(); settingsLink.hide();
favouritesLink.hide();
} }
}); });
}()); }());

View File

@@ -8,6 +8,11 @@
return (parent.attr('data-admin') !== "0"); return (parent.attr('data-admin') !== "0");
} }
function isUserBanned(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-banned') !== "" && parent.attr('data-banned') !== "0");
}
function getUID(element) { function getUID(element) {
var parent = $(element).parents('.users-box'); var parent = $(element).parents('.users-box');
return parent.attr('data-uid'); return parent.attr('data-uid');
@@ -34,6 +39,20 @@
deleteBtn.show(); deleteBtn.show();
}); });
jQuery('.ban-btn').each(function(index, element) {
var banBtn = $(element);
var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
if(isAdmin)
banBtn.addClass('disabled');
else if(isBanned)
banBtn.addClass('btn-warning');
else
banBtn.removeClass('btn-warning');
});
jQuery('.admin-btn').on('click', function() { jQuery('.admin-btn').on('click', function() {
var adminBtn = $(this); var adminBtn = $(this);
var isAdmin = isUserAdmin(adminBtn); var isAdmin = isUserAdmin(adminBtn);
@@ -74,6 +93,30 @@
return false; return false;
}); });
jQuery('.ban-btn').on('click', function() {
var banBtn = $(this);
var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
var parent = banBtn.parents('.users-box');
var uid = getUID(banBtn);
if(!isAdmin) {
if(isBanned) {
socket.emit('api:admin.user.unbanUser', uid);
banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') +'"?', function(confirm) {
socket.emit('api:admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
});
}
}
return false;
});
} }
@@ -171,9 +214,9 @@
$('#load-more-users-btn').on('click', loadMoreUsers); $('#load-more-users-btn').on('click', loadMoreUsers);
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var bottom = (document.body.offsetHeight - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > bottom && !loadingMoreUsers) { if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
loadMoreUsers(); loadMoreUsers();
} }
}); });

View File

@@ -8,7 +8,7 @@
facebook_url = templates.get('facebook-share-url'), facebook_url = templates.get('facebook-share-url'),
google_url = templates.get('google-share-url'), google_url = templates.get('google-share-url'),
loadingMoreTopics = false; loadingMoreTopics = false;
app.enter_room(room); app.enter_room(room);
twitterEl.addEventListener('click', function() { twitterEl.addEventListener('click', function() {
@@ -36,7 +36,6 @@
]); ]);
function onNewTopic(data) { function onNewTopic(data) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: [data] }), var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: [data] }),
topic = document.createElement('div'), topic = document.createElement('div'),
container = document.getElementById('topics-container'), container = document.getElementById('topics-container'),
@@ -49,7 +48,7 @@
topic.innerHTML = html; topic.innerHTML = html;
topic = topic.querySelector('a'); topic = topic.querySelector('a');
if (numTopics > 0) { if (numTopics > 0) {
for(x=0;x<numTopics;x++) { for(x=0;x<numTopics;x++) {
if (topics[x].querySelector('.icon-pushpin')) continue; if (topics[x].querySelector('.icon-pushpin')) continue;
@@ -61,6 +60,8 @@
container.insertBefore(topic, null); container.insertBefore(topic, null);
$(topic).hide().fadeIn('slow'); $(topic).hide().fadeIn('slow');
} }
socket.emit('api:categories.getRecentReplies', cid);
} }
socket.on('event:new_topic', onNewTopic); socket.on('event:new_topic', onNewTopic);
@@ -74,7 +75,7 @@
var recent_replies = document.getElementById('category_recent_replies'); var recent_replies = document.getElementById('category_recent_replies');
recent_replies.innerHTML = ''; recent_replies.innerHTML = '';
var frag = document.createDocumentFragment(), var frag = document.createDocumentFragment(),
li = document.createElement('li'); li = document.createElement('li');
for (var i=0,numPosts=posts.length; i<numPosts; i++) { for (var i=0,numPosts=posts.length; i<numPosts; i++) {
@@ -94,7 +95,7 @@
recent_replies.appendChild(frag); recent_replies.appendChild(frag);
} }
}); });
function onTopicsLoaded(topics) { function onTopicsLoaded(topics) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: topics }), var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: topics }),
@@ -105,14 +106,14 @@
container.append(html); container.append(html);
} }
function loadMoreTopics(cid) { function loadMoreTopics(cid) {
loadingMoreTopics = true; loadingMoreTopics = true;
socket.emit('api:category.loadMore', { socket.emit('api:category.loadMore', {
cid: cid, cid: cid,
after: $('#topics-container').children().length after: $('#topics-container').children().length
}, function(data) { }, function(data) {
if(data.topics.length) { if(data.topics.length) {
onTopicsLoaded(data.topics); onTopicsLoaded(data.topics);
@@ -122,9 +123,9 @@
} }
$(window).off('scroll').on('scroll', function(ev) { $(window).off('scroll').on('scroll', function(ev) {
var bottom = (document.body.offsetHeight - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > bottom && !loadingMoreTopics) { if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics(cid); loadMoreTopics(cid);
} }
}); });

View File

@@ -0,0 +1,11 @@
(function() {
$(document).ready(function() {
$('.user-favourite-posts .topic-row').on('click', function() {
ajaxify.go($(this).attr('topic-url'));
});
});
}());

View File

@@ -10,13 +10,7 @@
$('#no-followers-notice').show(); $('#no-followers-notice').show();
} }
$('.reputation').each(function(index, element) { app.addCommasToNumbers();
$(element).html(app.addCommas($(element).html()));
});
$('.postcount').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
}); });

View File

@@ -32,14 +32,7 @@
}); });
} }
$('.reputation').each(function(index, element) { app.addCommasToNumbers();
$(element).html(app.addCommas($(element).html()));
});
$('.postcount').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
}); });

View File

@@ -6,7 +6,7 @@
user_label = document.getElementById('user_label'), user_label = document.getElementById('user_label'),
active_record = document.getElementById('active_record'), active_record = document.getElementById('active_record'),
right_menu = document.getElementById('right-menu'); right_menu = document.getElementById('right-menu');
socket.emit('user.count', {}); socket.emit('user.count', {});
socket.on('user.count', function(data) { socket.on('user.count', function(data) {
num_users.innerHTML = "We currently have <b>" + data.count + "</b> registered users."; num_users.innerHTML = "We currently have <b>" + data.count + "</b> registered users.";
@@ -28,24 +28,28 @@
socket.emit('api:user.active.get'); socket.emit('api:user.active.get');
socket.on('api:user.active.get', function(data) { socket.on('api:user.active.get', function(data) {
var plural_users = parseInt(data.users) !== 1, var plural_users = parseInt(data.users) !== 1,
plural_anon = parseInt(data.anon) !== 1; plural_anon = parseInt(data.anon) !== 1;
active_users.innerHTML = 'There ' + (plural_users ? 'are' : 'is') + ' <strong>' + data.users + '</strong> user' + (plural_users ? 's' : '') + ' and <strong>' + data.anon + '</strong> guest' + (plural_anon ? 's' : '') + ' online'; active_users.innerHTML = 'There ' + (plural_users ? 'are' : 'is') + ' <strong>' + data.users + '</strong> user' + (plural_users ? 's' : '') + ' and <strong>' + data.anon + '</strong> guest' + (plural_anon ? 's' : '') + ' online';
}); });
socket.emit('api:user.active.get_record'); socket.emit('api:user.active.get_record');
socket.on('api:user.active.get_record', function(data) { socket.on('api:user.active.get_record', function(data) {
active_record.innerHTML = "most users ever online was <strong>" + data.record + "</strong> on <strong>" + (new Date(parseInt(data.timestamp,10))).toUTCString() + "</strong>"; active_record.innerHTML = "most users ever online was <strong>" + data.record + "</strong> on <strong>" + (new Date(parseInt(data.timestamp,10))).toUTCString() + "</strong>";
}); });
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] }); socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
socket.on('api:updateHeader', function(data) { socket.on('api:updateHeader', function(data) {
var rightMenu = $('#right-menu'); var rightMenu = $('#right-menu'),
if (data.uid > 0) { isLoggedIn = data.uid > 0;
if (isLoggedIn) {
jQuery('.nodebb-loggedin').show();
jQuery('.nodebb-loggedout').hide();
var userLabel = rightMenu.find('#user_label'); var userLabel = rightMenu.find('#user_label');
if(userLabel.length) { if(userLabel.length) {
@@ -55,8 +59,7 @@
userLabel.find('img').attr('src',data['picture']); userLabel.find('img').attr('src',data['picture']);
if(data['username']) if(data['username'])
userLabel.find('span').html(data['username']); userLabel.find('span').html(data['username']);
} } else {
else {
var userli = $('<li> \ var userli = $('<li> \
<a id="user_label" href="/users/'+data['userslug']+'"> \ <a id="user_label" href="/users/'+data['userslug']+'"> \
<img src="'+data['picture']+'"/> \ <img src="'+data['picture']+'"/> \
@@ -69,6 +72,9 @@
rightMenu.append(logoutli); rightMenu.append(logoutli);
} }
} else { } else {
jQuery('.nodebb-loggedin').hide();
jQuery('.nodebb-loggedout').show();
rightMenu.html(''); rightMenu.html('');
var registerEl = document.createElement('li'), var registerEl = document.createElement('li'),
@@ -90,10 +96,39 @@
notifTrigger.addEventListener('click', function(e) { notifTrigger.addEventListener('click', function(e) {
e.preventDefault(); e.preventDefault();
if (notifContainer.className.indexOf('open') === -1) { if (notifContainer.className.indexOf('open') === -1) {
socket.emit('api:notifications.get'); socket.emit('api:notifications.get', null, function(data) {
socket.emit('api:notifications.mark_all_read', null, function() { var notifFrag = document.createDocumentFragment(),
notifIcon.className = 'icon-circle-blank'; notifEl = document.createElement('li'),
utils.refreshTitle(); numRead = data.read.length,
numUnread = data.unread.length,
x;
notifList.innerHTML = '';
if ((data.read.length + data.unread.length) > 0) {
for(x=0;x<numUnread;x++) {
notifEl.setAttribute('data-nid', data.unread[x].nid);
notifEl.className = 'unread';
notifEl.innerHTML = '<a href="' + data.unread[x].path + '"><span class="pull-right">' + utils.relativeTime(data.unread[x].datetime, true) + '</span>' + data.unread[x].text + '</a>';
notifFrag.appendChild(notifEl.cloneNode(true));
}
for(x=0;x<numRead;x++) {
notifEl.setAttribute('data-nid', data.read[x].nid);
notifEl.className = '';
notifEl.innerHTML = '<a href="' + data.read[x].path + '"><span class="pull-right">' + utils.relativeTime(data.read[x].datetime, true) + '</span>' + data.read[x].text + '</a>';
notifFrag.appendChild(notifEl.cloneNode(true));
}
} else {
notifEl.innerHTML = '<a>You have no notifications</a>';
notifFrag.appendChild(notifEl);
}
notifList.appendChild(notifFrag);
if (data.unread.length > 0) notifIcon.className = 'icon-circle active';
else notifIcon.className = 'icon-circle-blank';
socket.emit('api:notifications.mark_all_read', null, function() {
notifIcon.className = 'icon-circle-blank';
utils.refreshTitle();
});
}); });
} }
}); });
@@ -109,37 +144,15 @@
if (nid > 0) socket.emit('api:notifications.mark_read', nid); if (nid > 0) socket.emit('api:notifications.mark_read', nid);
} }
}); });
socket.on('api:notifications.get', function(data) {
var notifFrag = document.createDocumentFragment(),
notifEl = document.createElement('li'),
numRead = data.read.length,
numUnread = data.unread.length,
x;
notifList.innerHTML = '';
if ((data.read.length + data.unread.length) > 0) {
for(x=0;x<numUnread;x++) {
notifEl.setAttribute('data-nid', data.unread[x].nid);
notifEl.className = 'unread';
notifEl.innerHTML = '<a href="' + data.unread[x].path + '"><span class="pull-right">' + utils.relativeTime(data.unread[x].datetime, true) + '</span>' + data.unread[x].text + '</a>';
notifFrag.appendChild(notifEl.cloneNode(true));
}
for(x=0;x<numRead;x++) {
notifEl.setAttribute('data-nid', data.read[x].nid);
notifEl.className = '';
notifEl.innerHTML = '<a href="' + data.read[x].path + '"><span class="pull-right">' + utils.relativeTime(data.read[x].datetime, true) + '</span>' + data.read[x].text + '</a>';
notifFrag.appendChild(notifEl.cloneNode(true));
}
} else {
notifEl.innerHTML = '<a>You have no notifications</a>';
notifFrag.appendChild(notifEl);
}
notifList.appendChild(notifFrag);
if (data.unread.length > 0) notifIcon.className = 'icon-circle active';
else notifIcon.className = 'icon-circle-blank';
});
socket.on('event:new_notification', function() { socket.on('event:new_notification', function() {
document.querySelector('.notifications a i').className = 'icon-circle active'; document.querySelector('.notifications a i').className = 'icon-circle active';
app.alert({
alert_id: 'new_notif',
title: 'New notification',
message: 'You have unread notifications.',
type: 'notify',
timeout: 2000
});
utils.refreshTitle(); utils.refreshTitle();
}); });
@@ -148,12 +161,12 @@
var username = data.username; var username = data.username;
var fromuid = data.fromuid; var fromuid = data.fromuid;
var message = data.message; var message = data.message;
require(['chat'], function(chat) { require(['chat'], function(chat) {
var chatModal = chat.createModalIfDoesntExist(username, fromuid); var chatModal = chat.createModalIfDoesntExist(username, fromuid);
chatModal.show(); chatModal.show();
chat.bringModalToTop(chatModal); chat.bringModalToTop(chatModal);
chat.appendChatMessage(chatModal, message); chat.appendChatMessage(chatModal, message);
}); });
}); });

View File

@@ -12,26 +12,29 @@
if (target) { if (target) {
document.location.href = target.getAttribute('data-url'); document.location.href = target.getAttribute('data-url');
} }
}); });
$('#login').on('click', function() { $('#login').on('click', function() {
var loginData = { var loginData = {
'username': $('#username').val(), 'username': $('#username').val(),
'password': $('#password').val(), 'password': $('#password').val(),
'_csrf': $('#csrf-token').val() '_csrf': $('#csrf-token').val()
}; };
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: RELATIVE_PATH + '/login', url: RELATIVE_PATH + '/login',
data: loginData, data: loginData,
success: function(data, textStatus, jqXHR) { success: function(data, textStatus, jqXHR) {
$('#login-error-notify').hide(); if(!data.success) {
window.location.replace(RELATIVE_PATH + "/?loggedin"); $('#login-error-notify').html(data.message).show();
} else {
$('#login-error-notify').hide();
window.location.replace(RELATIVE_PATH + "/?loggedin");
}
}, },
error : function(data, textStatus, jqXHR) { error : function(data, textStatus, jqXHR) {
$('#login-error-notify').show().delay(1000).fadeOut(250); $('#login-error-notify').show();
}, },
dataType: 'json', dataType: 'json',
async: true, async: true,
@@ -40,5 +43,6 @@
return false; return false;
}); });
document.querySelector('#content input').focus();
}()); }());

View File

@@ -69,9 +69,9 @@
} }
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var bottom = (document.body.offsetHeight - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > bottom && !loadingMoreTopics) { if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics(); loadMoreTopics();
} }
}); });

View File

@@ -9,12 +9,12 @@
pinned: templates.get('pinned') pinned: templates.get('pinned')
}, },
topic_name = templates.get('topic_name'); topic_name = templates.get('topic_name');
jQuery('document').ready(function() { jQuery('document').ready(function() {
app.addCommasToNumbers(); app.addCommasToNumbers();
var room = 'topic_' + tid, var room = 'topic_' + tid,
adminTools = document.getElementById('thread-tools'); adminTools = document.getElementById('thread-tools');
@@ -26,17 +26,12 @@
if (thread_state.pinned === '1') set_pinned_state(true); if (thread_state.pinned === '1') set_pinned_state(true);
if (expose_tools === '1') { if (expose_tools === '1') {
var deleteThreadEl = document.getElementById('delete_thread'), var moveThreadModal = $('#move_thread_modal');
lockThreadEl = document.getElementById('lock_thread'),
pinThreadEl = document.getElementById('pin_thread'),
moveThreadEl = document.getElementById('move_thread'),
moveThreadModal = $('#move_thread_modal');
adminTools.style.visibility = 'inherit'; adminTools.style.visibility = 'inherit';
// Add events to the thread tools // Add events to the thread tools
deleteThreadEl.addEventListener('click', function(e) { $('#delete_thread').on('click', function(e) {
e.preventDefault();
if (thread_state.deleted !== '1') { if (thread_state.deleted !== '1') {
bootbox.confirm('Are you sure you want to delete this thread?', function(confirm) { bootbox.confirm('Are you sure you want to delete this thread?', function(confirm) {
if (confirm) socket.emit('api:topic.delete', { tid: tid }); if (confirm) socket.emit('api:topic.delete', { tid: tid });
@@ -46,30 +41,31 @@
if (confirm) socket.emit('api:topic.restore', { tid: tid }); if (confirm) socket.emit('api:topic.restore', { tid: tid });
}); });
} }
}, false); return false;
});
lockThreadEl.addEventListener('click', function(e) { $('#lock_thread').on('click', function(e) {
e.preventDefault();
if (thread_state.locked !== '1') { if (thread_state.locked !== '1') {
socket.emit('api:topic.lock', { tid: tid }); socket.emit('api:topic.lock', { tid: tid });
} else { } else {
socket.emit('api:topic.unlock', { tid: tid }); socket.emit('api:topic.unlock', { tid: tid });
} }
}, false); return false;
});
pinThreadEl.addEventListener('click', function(e) { $('#pin_thread').on('click', function(e) {
e.preventDefault();
if (thread_state.pinned !== '1') { if (thread_state.pinned !== '1') {
socket.emit('api:topic.pin', { tid: tid }); socket.emit('api:topic.pin', { tid: tid });
} else { } else {
socket.emit('api:topic.unpin', { tid: tid }); socket.emit('api:topic.unpin', { tid: tid });
} }
}, false); return false;
});
moveThreadEl.addEventListener('click', function(e) { $('#move_thread').on('click', function(e) {
e.preventDefault();
moveThreadModal.modal('show'); moveThreadModal.modal('show');
}, false); return false;
});
moveThreadModal.on('shown', function() { moveThreadModal.on('shown', function() {
var loadingEl = document.getElementById('categories-loading'); var loadingEl = document.getElementById('categories-loading');
@@ -195,16 +191,18 @@
}); });
} }
}); });
socket.emit('api:topic.followCheck', tid); socket.emit('api:topic.followCheck', tid);
followEl[0].addEventListener('click', function() { if(followEl[0]) {
socket.emit('api:topic.follow', tid); followEl[0].addEventListener('click', function() {
}, false); socket.emit('api:topic.follow', tid);
}, false);
}
// Infinite scrolling of posts
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var bottom = (document.body.offsetHeight - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) { if ($(window).scrollTop() > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) {
app.loadMorePosts(tid); app.loadMorePosts(tid);
} }
}); });
@@ -254,11 +252,9 @@
var element = $(this).find('i'); var element = $(this).find('i');
if(element.attr('class') == 'icon-star-empty') { if(element.attr('class') == 'icon-star-empty') {
element.attr('class', 'icon-star');
socket.emit('api:posts.favourite', {pid: pid, room_id: app.current_room}); socket.emit('api:posts.favourite', {pid: pid, room_id: app.current_room});
} }
else { else {
element.attr('class', 'icon-star-empty');
socket.emit('api:posts.unfavourite', {pid: pid, room_id: app.current_room}); socket.emit('api:posts.unfavourite', {pid: pid, room_id: app.current_room});
} }
}); });
@@ -284,19 +280,21 @@
socket.emit('api:posts.delete', { pid: pid }) : socket.emit('api:posts.delete', { pid: pid }) :
socket.emit('api:posts.restore', { pid: pid }); socket.emit('api:posts.restore', { pid: pid });
} }
}); });
$('.post-container').delegate('.chat', 'click', function(e) { $('.post-container').delegate('.chat', 'click', function(e) {
var username = $(this).parents('li').attr('data-username'); var username = $(this).parents('li').attr('data-username');
var touid = $(this).parents('li').attr('data-uid'); var touid = $(this).parents('li').attr('data-uid');
require(['chat'], function(chat){ if(username === app.username || !app.username)
return;
require(['chat'], function(chat) {
var chatModal = chat.createModalIfDoesntExist(username, touid); var chatModal = chat.createModalIfDoesntExist(username, touid);
chatModal.show(); chatModal.show();
chat.bringModalToTop(chatModal); chat.bringModalToTop(chatModal);
}); });
}); });
ajaxify.register_events([ ajaxify.register_events([
@@ -372,7 +370,7 @@
var editedPostEl = document.getElementById('content_' + data.pid); var editedPostEl = document.getElementById('content_' + data.pid);
var editedPostTitle = $('#topic_title_'+data.pid); var editedPostTitle = $('#topic_title_'+data.pid);
if(editedPostTitle.length > 0) { if(editedPostTitle.length > 0) {
editedPostTitle.fadeOut(250, function() { editedPostTitle.fadeOut(250, function() {
editedPostTitle.html(data.title); editedPostTitle.html(data.title);
@@ -387,9 +385,22 @@
}); });
socket.on('api:posts.favourite', function(data) { socket.on('api:posts.favourite', function(data) {
if (data.status !== 'ok' && data.pid) { if (data.status === 'ok' && data.pid) {
var favEl = document.querySelector('.post_rep_' + data.pid).nextSibling; var favEl = document.querySelector('.post_rep_' + data.pid).nextSibling;
if (favEl) favEl.className = 'icon-star-empty'; if (favEl) {
favEl.className = 'icon-star';
$(favEl).parent().addClass('btn-warning');
}
}
});
socket.on('api:posts.unfavourite', function(data) {
if (data.status === 'ok' && data.pid) {
var favEl = document.querySelector('.post_rep_' + data.pid).nextSibling;
if (favEl) {
favEl.className = 'icon-star-empty';
$(favEl).parent().removeClass('btn-warning');
}
} }
}); });
@@ -582,7 +593,7 @@
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')), var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
editEl = postEl.find('.edit'), editEl = postEl.find('.edit'),
deleteEl = postEl.find('.delete'); deleteEl = postEl.find('.delete');
if (state) { if (state) {
editEl.removeClass('none'); editEl.removeClass('none');
deleteEl.removeClass('none'); deleteEl.removeClass('none');

View File

@@ -56,6 +56,10 @@
$('#topics-container').empty(); $('#topics-container').empty();
$('#category-no-topics').removeClass('hidden'); $('#category-no-topics').removeClass('hidden');
app.alertSuccess('All topics marked as read!'); app.alertSuccess('All topics marked as read!');
$('#numUnreadBadge')
.removeClass('badge-important')
.addClass('badge-inverse')
.html('0');
} else { } else {
app.alertError('There was an error marking topics read!'); app.alertError('There was an error marking topics read!');
} }
@@ -78,21 +82,24 @@
if(data.topics && data.topics.length) { if(data.topics && data.topics.length) {
onTopicsLoaded(data.topics); onTopicsLoaded(data.topics);
$('#topics-container').attr('data-next-start', data.nextStart); $('#topics-container').attr('data-next-start', data.nextStart);
} else {
$('#load-more-btn').hide();
} }
loadingMoreTopics = false; loadingMoreTopics = false;
}); });
} }
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var bottom = (document.body.offsetHeight - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > bottom && !loadingMoreTopics) { if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics(); loadMoreTopics();
} }
}); });
if($("body").height() <= $(window).height() && $('#topics-container').children().length) if($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20)
$('#load-more-btn').show(); $('#load-more-btn').show();
$('#load-more-btn').on('click', function() { $('#load-more-btn').on('click', function() {

View File

@@ -8,6 +8,8 @@
parts = url.split('/'), parts = url.split('/'),
active = parts[parts.length-1]; active = parts[parts.length-1];
app.addCommasToNumbers();
jQuery('.nav-pills li').removeClass('active'); jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() { jQuery('.nav-pills li a').each(function() {
if (this.getAttribute('href').match(active)) { if (this.getAttribute('href').match(active)) {
@@ -67,13 +69,7 @@
}); });
$('.reputation').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
$('.postcount').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
function onUsersLoaded(users) { function onUsersLoaded(users) {
var html = templates.prepare(templates['users'].blocks['users']).parse({ users: users }); var html = templates.prepare(templates['users'].blocks['users']).parse({ users: users });
@@ -109,9 +105,9 @@
$('#load-more-users-btn').on('click', loadMoreUsers); $('#load-more-users-btn').on('click', loadMoreUsers);
$(window).off('scroll').on('scroll', function() { $(window).off('scroll').on('scroll', function() {
var bottom = (document.body.offsetHeight - $(window).height()) * 0.9; var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > bottom && !loadingMoreUsers) { if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
loadMoreUsers(); loadMoreUsers();
} }
}); });

View File

@@ -1,8 +1,7 @@
define(['taskbar'], function(taskbar) { define(['taskbar'], function(taskbar) {
var module = {}; var module = {};
module.bringModalToTop = function(chatModal) { module.bringModalToTop = function(chatModal) {
var topZ = 0; var topZ = 0;
$('.modal').each(function() { $('.modal').each(function() {
@@ -11,7 +10,7 @@ define(['taskbar'], function(taskbar) {
topZ = thisZ; topZ = thisZ;
} }
}); });
chatModal.css('zIndex', topZ+1); chatModal.css('zIndex', topZ + 1);
} }
module.createModalIfDoesntExist = function(username, touid) { module.createModalIfDoesntExist = function(username, touid) {
@@ -39,7 +38,9 @@ define(['taskbar'], function(taskbar) {
module.bringModalToTop(chatModal); module.bringModalToTop(chatModal);
}); });
addSendHandler(chatModal, touid); addSendHandler(chatModal, touid);
getChatMessages(chatModal, touid);
} }
taskbar.push('chat', chatModal.attr('UUID'), {title:'chat with '+username}); taskbar.push('chat', chatModal.attr('UUID'), {title:'chat with '+username});
@@ -58,6 +59,14 @@ define(['taskbar'], function(taskbar) {
taskbar.minimize('chat', uuid); taskbar.minimize('chat', uuid);
} }
function getChatMessages(chatModal, touid) {
socket.emit('getChatMessages', {touid:touid}, function(messages) {
for(var i = 0; i<messages.length; ++i) {
module.appendChatMessage(chatModal, messages[i].content);
}
});
}
function addSendHandler(chatModal, touid) { function addSendHandler(chatModal, touid) {
chatModal.find('#chat-message-input').off('keypress'); chatModal.find('#chat-message-input').off('keypress');
chatModal.find('#chat-message-input').on('keypress', function(e) { chatModal.find('#chat-message-input').on('keypress', function(e) {

View File

@@ -7,12 +7,29 @@ define(['taskbar'], function(taskbar) {
postContainer: undefined, postContainer: undefined,
}; };
function createImageLabel(img, postImages) {
var imageLabel = $('<div class="label"><span>'+ img.name +'</span></div>');
var closeButton = $('<button class="close">&times;</button>');
closeButton.on('click', function(e) {
imageLabel.remove();
var index = postImages.indexOf(img);
if(index !== -1) {
postImages.splice(index, 1);
}
});
imageLabel.append(closeButton);
return imageLabel;
}
function loadFile(file) { function loadFile(file) {
var reader = new FileReader(); var reader = new FileReader(),
var dropDiv = $('#imagedrop'); dropDiv = $('.post-window .imagedrop'),
var imagelist = $('#imagelist'); imagelist = $('.post-window .imagelist'),
var uuid = dropDiv.parents('[data-uuid]').attr('data-uuid'); uuid = dropDiv.parents('[data-uuid]').attr('data-uuid'),
var posts = composer.posts[uuid]; posts = composer.posts[uuid];
$(reader).on('loadend', function(e) { $(reader).on('loadend', function(e) {
var bin = this.result; var bin = this.result;
@@ -25,19 +42,9 @@ define(['taskbar'], function(taskbar) {
posts.images.push(img); posts.images.push(img);
var imageLabel = $('<div class="label"><span>'+ file.name +'</span></div>'); var imageLabel = createImageLabel(img, posts.images);
var closeButton = $('<button class="close">&times;</button>');
closeButton.on('click', function(e) {
imageLabel.remove();
var index = posts.images.indexOf(img);
if(index !== -1) {
posts.images.splice(index, 1);
}
});
imageLabel.append(closeButton); imagelist.append(imageLabel);
imagelist.append(imageLabel);
dropDiv.hide(); dropDiv.hide();
}); });
@@ -46,12 +53,29 @@ define(['taskbar'], function(taskbar) {
function initializeFileReader() { function initializeFileReader() {
jQuery.event.props.push( "dataTransfer" ); jQuery.event.props.push( "dataTransfer" );
if(window.FileReader) { var draggingDocument = false;
var drop = $('#imagedrop');
$(composer.postContainer).on('dragenter dragover', function() { if(window.FileReader) {
drop.show(); var drop = $('.post-window .imagedrop'),
textarea = $('.post-window textarea');
$(document).on('dragstart', function(e) {
draggingDocument = true;
}).on('dragend', function(e) {
draggingDocument = false;
});
textarea.on('dragenter', function(e) {
if(draggingDocument)
return;
drop.css('top', textarea.position().top + 'px');
drop.show();
drop.on('dragleave', function(ev) {
drop.hide();
drop.off('dragleave');
});
}); });
function cancel(e) { function cancel(e) {
@@ -61,22 +85,22 @@ define(['taskbar'], function(taskbar) {
drop.on('dragover', cancel); drop.on('dragover', cancel);
drop.on('dragenter', cancel); drop.on('dragenter', cancel);
drop.on('drop', function(e) { drop.on('drop', function(e) {
e.preventDefault(); e.preventDefault();
var uuid = drop.parents('[data-uuid]').attr('data-uuid'); var uuid = drop.parents('[data-uuid]').attr('data-uuid'),
var posts = composer.posts[uuid]; posts = composer.posts[uuid],
dt = e.dataTransfer,
var dt = e.dataTransfer; files = dt.files;
var files = dt.files;
for (var i=0; i<files.length; i++) { for (var i=0; i<files.length; i++) {
loadFile(files[i]); loadFile(files[i]);
} }
if(!files.length)
drop.hide();
return false; return false;
}); });
} }
} }
@@ -88,21 +112,19 @@ define(['taskbar'], function(taskbar) {
composer.postContainer.className = 'post-window row-fluid'; composer.postContainer.className = 'post-window row-fluid';
composer.postContainer.innerHTML = '<div class="span5">' + composer.postContainer.innerHTML = '<div class="span5">' +
'<input type="text" tabIndex="1" placeholder="Enter your topic title here..." />' + '<input type="text" tabIndex="1" placeholder="Enter your topic title here..." />' +
'<div class="btn-toolbar">' + '<div class="btn-toolbar formatting-bar">' +
'<div class="btn-group formatting-bar">' + '<div class="btn-group">' +
'<span class="btn btn-link" tabindex="-1"><i class="icon-bold"></i></span>' + '<span class="btn btn-link" tabindex="-1"><i class="icon-bold"></i></span>' +
'<span class="btn btn-link" tabindex="-1"><i class="icon-italic"></i></span>' + '<span class="btn btn-link" tabindex="-1"><i class="icon-italic"></i></span>' +
'<span class="btn btn-link" tabindex="-1"><i class="icon-list"></i></span>' + '<span class="btn btn-link" tabindex="-1"><i class="icon-list"></i></span>' +
'<span class="btn btn-link" tabindex="-1"><i class="icon-link"></i></span>' + '<span class="btn btn-link" tabindex="-1"><i class="icon-link"></i></span>' +
'</div>' + '</div>' +
'</div>' + '</div>' +
'<div style="position:relative;">'+ '<textarea tabIndex="2"></textarea>' +
'<div id="imagedrop" class=""><div>Drag and Drop Images Here</div></div>'+ '<div class="imagelist"></div>'+
'<textarea tabIndex="2"></textarea>' + '<div class="imagedrop"><div>Drag and Drop Images Here</div></div>'+
'<div id="imagelist"></div>'+ '<div class="btn-toolbar action-bar">' +
'</div>'+ '<div class="btn-group" style="float: right; margin-right: -8px">' +
'<div class="btn-toolbar">' +
'<div class="btn-group action-bar" style="float: right; margin-right: -8px">' +
'<button data-action="minimize" class="btn hidden-phone" tabIndex="4"><i class="icon-download-alt"></i> Minimize</button>' + '<button data-action="minimize" class="btn hidden-phone" tabIndex="4"><i class="icon-download-alt"></i> Minimize</button>' +
'<button class="btn" data-action="discard" tabIndex="5"><i class="icon-remove"></i> Discard</button>' + '<button class="btn" data-action="discard" tabIndex="5"><i class="icon-remove"></i> Discard</button>' +
'<button data-action="post" class="btn" tabIndex="3"><i class="icon-ok"></i> Submit</button>' + '<button data-action="post" class="btn" tabIndex="3"><i class="icon-ok"></i> Submit</button>' +
@@ -112,7 +134,8 @@ define(['taskbar'], function(taskbar) {
document.body.insertBefore(composer.postContainer, taskbar); document.body.insertBefore(composer.postContainer, taskbar);
initializeFileReader(); if(config.imgurClientIDSet)
initializeFileReader();
socket.on('api:composer.push', function(threadData) { socket.on('api:composer.push', function(threadData) {
if (!threadData.error) { if (!threadData.error) {
@@ -152,12 +175,14 @@ define(['taskbar'], function(taskbar) {
// Post Window events // Post Window events
var jPostContainer = $(composer.postContainer), var jPostContainer = $(composer.postContainer),
postContentEl = composer.postContainer.querySelector('textarea') postContentEl = composer.postContainer.querySelector('textarea');
jPostContainer.on('change', 'input, textarea', function() { jPostContainer.on('change', 'input, textarea', function() {
var uuid = $(this).parents('.post-window')[0].getAttribute('data-uuid'); var uuid = $(this).parents('.post-window')[0].getAttribute('data-uuid');
if (this.nodeName === 'INPUT') composer.posts[uuid].title = this.value; if (this.nodeName === 'INPUT') composer.posts[uuid].title = this.value;
else if (this.nodeName === 'TEXTAREA') composer.posts[uuid].body = this.value; else if (this.nodeName === 'TEXTAREA') composer.posts[uuid].body = this.value;
}); });
jPostContainer.on('click', '.action-bar button', function() { jPostContainer.on('click', '.action-bar button', function() {
var action = this.getAttribute('data-action'), var action = this.getAttribute('data-action'),
uuid = $(this).parents('.post-window').attr('data-uuid'); uuid = $(this).parents('.post-window').attr('data-uuid');
@@ -167,6 +192,7 @@ define(['taskbar'], function(taskbar) {
case 'discard': composer.discard(uuid); break; case 'discard': composer.discard(uuid); break;
} }
}); });
jPostContainer.on('click', '.formatting-bar span', function() { jPostContainer.on('click', '.formatting-bar span', function() {
var iconClass = this.querySelector('i').className, var iconClass = this.querySelector('i').className,
cursorEnd = postContentEl.value.length, cursorEnd = postContentEl.value.length,
@@ -239,15 +265,22 @@ define(['taskbar'], function(taskbar) {
}); });
} }
function createPostImages(images) {
var imagelist = $(composer.postContainer).find('.imagelist');
imagelist.empty();
if(images && images.length) {
for(var i=0; i<images.length; ++i) {
var imageLabel = createImageLabel(images[i], images);
imagelist.append(imageLabel);
}
}
}
composer.load = function(post_uuid) { composer.load = function(post_uuid) {
var post_data = composer.posts[post_uuid], var post_data = composer.posts[post_uuid],
titleEl = composer.postContainer.querySelector('input'), titleEl = composer.postContainer.querySelector('input'),
bodyEl = composer.postContainer.querySelector('textarea'), bodyEl = composer.postContainer.querySelector('textarea');
dropDiv = $(composer.postContainer).find('#imagedrop'),
imagelist = $(composer.postContainer).find('#imagelist');
dropDiv.hide();
imagelist.empty();
composer.reposition(post_uuid); composer.reposition(post_uuid);
composer.active = post_uuid; composer.active = post_uuid;
@@ -264,7 +297,9 @@ define(['taskbar'], function(taskbar) {
titleEl.value = post_data.title; titleEl.value = post_data.title;
titleEl.readOnly = false; titleEl.readOnly = false;
} }
bodyEl.value = post_data.body bodyEl.value = post_data.body;
createPostImages(post_data.images);
// Direct user focus to the correct element // Direct user focus to the correct element
if ((parseInt(post_data.tid) || parseInt(post_data.pid)) > 0) { if ((parseInt(post_data.tid) || parseInt(post_data.pid)) > 0) {
@@ -292,7 +327,7 @@ define(['taskbar'], function(taskbar) {
composer.post = function(post_uuid) { composer.post = function(post_uuid) {
// Check title and post length // Check title and post length
var postData = composer.posts[post_uuid], var postData = composer.posts[post_uuid],
titleEl = composer.postContainer.querySelector('input'), titleEl = composer.postContainer.querySelector('input'),
bodyEl = composer.postContainer.querySelector('textarea'); bodyEl = composer.postContainer.querySelector('textarea');
@@ -347,7 +382,8 @@ define(['taskbar'], function(taskbar) {
composer.discard = function(post_uuid) { composer.discard = function(post_uuid) {
if (composer.posts[post_uuid]) { if (composer.posts[post_uuid]) {
$(composer.postContainer).find('#imagedrop').html(''); $(composer.postContainer).find('.imagedrop').hide();
$(composer.postContainer).find('.imagelist').empty();
delete composer.posts[post_uuid]; delete composer.posts[post_uuid];
composer.minimize(); composer.minimize();
taskbar.discard('composer', post_uuid); taskbar.discard('composer', post_uuid);

View File

@@ -181,7 +181,7 @@
if (!templates[tpl_url] || !template_data) return; if (!templates[tpl_url] || !template_data) return;
if(typeof global !== "undefined") if(typeof global !== "undefined")
template_data['relative_path'] = global.nconf.get('relative_path'); template_data['relative_path'] = nconf.get('relative_path');
else else
template_data['relative_path'] = RELATIVE_PATH; template_data['relative_path'] = RELATIVE_PATH;

View File

@@ -126,13 +126,38 @@
return tags; return tags;
}, },
refreshTitle: function() { refreshTitle: function(url) {
var a = document.createElement('a'); if (!url) {
var a = document.createElement('a');
a.href = document.location;
url = a.pathname.slice(1);
}
a.href = document.location; socket.emit('api:meta.buildTitle', url, function(title, numNotifications) {
socket.emit('api:meta.buildTitle', a.pathname.slice(1), function(title) { document.title = (numNotifications > 0 ? '(' + numNotifications + ') ' : '') + title;
document.title = title; if (numNotifications > 0) document.querySelector('.notifications a i').className = 'icon-circle active';
}); });
jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) {
var badge = jQuery('#numUnreadBadge');
badge.html(data.count > 20 ? '20+' : data.count);
if (data.count > 0) {
badge
.removeClass('badge-inverse')
.addClass('badge-important')
}
else {
badge
.removeClass('badge-important')
.addClass('badge-inverse')
}
});
},
isRelativeUrl: function(url) {
var firstChar = url.slice(0, 1);
return (firstChar === '.' || firstChar === '/');
} }
} }

View File

@@ -1,55 +1,48 @@
<div class="well"> <div class="well">
<div class="account-username-box" data-userslug="{userslug}">
<div class="account-username-box">
<span class="account-username"> <span class="account-username">
<a href="/users/{userslug}">{username}</a> <a href="/users/{userslug}">{username}</a>
</span> </span>
<div class="account-sub-links inline-block pull-right">
<span id="settingsLink" class="pull-right"><a href="/users/{userslug}/settings">settings</a></span>
<span class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<span id="editLink" class="pull-right"><a href="/users/{userslug}/edit">edit</a></span>
</div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="span2" style="text-align: center; margin-bottom:20px;"> <div class="span2 account-block" style="text-align: center; margin-bottom:20px;">
<div class="account-picture-block"> <div class="account-picture-block">
<img src="{picture}" class="user-profile-picture img-polaroid"/> <img src="{picture}" class="user-profile-picture img-polaroid"/>
</div> </div>
<div class="account-online-status"> <div class="account-online-status">
<span><i class="icon-circle-blank"></i> <span>offline</span></span> <span><i class="icon-circle-blank"></i> <span>offline</span></span>
</div> </div>
<div class="{show_banned}">
<span class="label label-important">banned</span>
</div>
<div id="user-actions"> <div id="user-actions">
<a id="follow-btn" href="#" class="btn hide">Follow</a> <a id="follow-btn" href="#" class="btn hide">Follow</a>
<a id="unfollow-btn" href="#" class="btn hide">Unfollow</a> <a id="unfollow-btn" href="#" class="btn hide">Unfollow</a>
</div> </div>
</div> </div>
<div class="span4"> <div class="span4">
<h4>profile</h4>
<div class="inline-block"> <div class="inline-block">
<div class="account-bio-block"> <div class="account-bio-block">
<span class="account-bio-label">email</span><i class="icon-eye-close {emailClass}" title="Email hidden"></i> <span class="account-bio-label">email</span><i class="icon-eye-close {emailClass}" title="Email hidden"></i>
<span>{email}</span> <span>{email}</span>
<br/> <br/>
<span class="account-bio-label">full name</span> <span class="account-bio-label">full name</span>
<span>{fullname}</span> <span>{fullname}</span>
<br/> <br/>
<span class="account-bio-label">website</span> <span class="account-bio-label">website</span>
<span><a href="{website}">{website}</a></span> <span><a href="{website}">{website}</a></span>
<br/> <br/>
<span class="account-bio-label">location</span> <span class="account-bio-label">location</span>
<span>{location}</span> <span>{location}</span>
<br/> <br/>
<span class="account-bio-label">age</span> <span class="account-bio-label">age</span>
<span>{age}</span> <span>{age}</span>
<br/> <br/>
@@ -58,22 +51,26 @@
<span>{joindate}</span> <span>{joindate}</span>
<br/> <br/>
<span class="account-bio-label">profile views</span>
<span class="formatted-number">{profileviews}</span>
<br/>
<span class="account-bio-label">reputation</span> <span class="account-bio-label">reputation</span>
<span id='reputation'>{reputation}</span> <span class="formatted-number">{reputation}</span>
<br/> <br/>
<span class="account-bio-label">posts</span> <span class="account-bio-label">posts</span>
<span id='postcount'>{postcount}</span> <span class="formatted-number">{postcount}</span>
<br/> <br/>
<span class="account-bio-label">followers</span> <span class="account-bio-label">followers</span>
<span>{followerCount}</span> <span class="formatted-number">{followerCount}</span>
<br/> <br/>
<span class="account-bio-label">following</span> <span class="account-bio-label">following</span>
<span>{followingCount}</span> <span class="formatted-number">{followingCount}</span>
<br/> <br/>
<hr/> <hr/>
<span class="account-bio-label">signature</span> <span class="account-bio-label">signature</span>
<div class="post-signature"> <div class="post-signature">
@@ -82,14 +79,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="span6 user-recent-posts"> <div class="span6 user-recent-posts">
<h4>recent posts </h4>
<!-- BEGIN posts --> <!-- BEGIN posts -->
<div class="topic-row img-polaroid clearfix" topic-url="topic/{posts.tid}/#{posts.pid}"> <div class="topic-row img-polaroid clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
<span>{posts.content}</span> <span>{posts.content}</span>
<span class="pull-right">{posts.relativeTime} ago</span> <span class="pull-right">{posts.relativeTime} ago</span>
</div> </div>
<!-- END posts --> <!-- END posts -->
</div> </div>
</div> </div>

View File

@@ -60,17 +60,11 @@
</div> </div>
</div> </div>
<div class="account-username-box"> <div class="account-username-box" data-userslug="{userslug}">
<span class="account-username"> <span class="account-username">
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i> <a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/edit">edit</a> <a href="/users/{userslug}/edit">edit</a>
</span> </span>
<div class="account-sub-links inline-block pull-right">
<span id="settingsLink" class="pull-right"><a href="/users/{userslug}/settings">settings</a></span>
<span class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<span id="editLink" class="pull-right"><a href="/users/{userslug}/edit">edit</a></span>
</div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
@@ -171,4 +165,5 @@
<input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" /> <input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" />
<input type="hidden" template-variable="uploadedpicture" value="{uploadedpicture}" /> <input type="hidden" template-variable="uploadedpicture" value="{uploadedpicture}" />
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountedit.js"></script> <script type="text/javascript" src="{relative_path}/src/forum/accountedit.js"></script>

View File

@@ -1,17 +1,11 @@
<div class="well"> <div class="well">
<div class="account-username-box"> <div class="account-username-box" data-userslug="{userslug}">
<span class="account-username"> <span class="account-username">
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i> <a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/settings">settings</a> <a href="/users/{userslug}/settings">settings</a>
</span> </span>
<div class="account-sub-links inline-block pull-right">
<span id="settingsLink" class="pull-right"><a href="/users/{userslug}/settings">settings</a></span>
<span class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<span id="editLink" class="pull-right"><a href="/users/{userslug}/edit">edit</a></span>
</div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
@@ -31,4 +25,5 @@
</div> </div>
</div> </div>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountsettings.js"></script> <script type="text/javascript" src="{relative_path}/src/forum/accountsettings.js"></script>

View File

@@ -27,6 +27,8 @@
</script> </script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css"> <link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
<script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script> <script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script src="{relative_path}/src/utils.js"></script>
<link rel="stylesheet" type="text/css" href="{relative_path}/css/style.css" /> <link rel="stylesheet" type="text/css" href="{relative_path}/css/style.css" />
<link rel="stylesheet" type="text/css" href="{relative_path}/css/admin.css" /> <link rel="stylesheet" type="text/css" href="{relative_path}/css/admin.css" />
</head> </head>

View File

@@ -8,6 +8,8 @@
<input type="text" placeholder="Your Community Name" data-field="title" /> <input type="text" placeholder="Your Community Name" data-field="title" />
<label>Site Description</label> <label>Site Description</label>
<input type="text" class="input-xxlarge" placeholder="A short description about your community" data-field="description" /> <input type="text" class="input-xxlarge" placeholder="A short description about your community" data-field="description" />
<label>Imgur Client ID</label>
<input type="text" class="input-xxlarge" placeholder="Imgur ClientID for image uploads" data-field="imgurClientID" />
</form> </form>
</div> </div>

View File

@@ -16,7 +16,7 @@
<ul id="users-container" class="users"> <ul id="users-container" class="users">
<!-- BEGIN users --> <!-- BEGIN users -->
<div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}"> <div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}" data-banned="{users.banned}">
<a href="/users/{users.userslug}"> <a href="/users/{users.userslug}">
<img src="{users.picture}" class="img-polaroid"/> <img src="{users.picture}" class="img-polaroid"/>
</a> </a>
@@ -37,6 +37,9 @@
<div> <div>
<a href="#" class="btn delete-btn btn-danger">Delete</a> <a href="#" class="btn delete-btn btn-danger">Delete</a>
</div> </div>
<div>
<a href="#" class="btn ban-btn">Ban</a>
</div>
</div> </div>
<!-- END users --> <!-- END users -->
</ul> </ul>

View File

@@ -10,13 +10,8 @@
<div id="category_active_users"></div> <div id="category_active_users"></div>
</ul> </ul>
</div> </div>
<div class="alert alert-warning hide {no_topics_message}" id="category-no-topics">
<strong>There are no topics in this category.</strong><br />
Why don't you try posting one?
</div>
<div> <div>
<button id="new_post" class="btn btn-primary btn-large {show_topic_button}">New Topic</button> <button id="new_post" class="btn btn-primary btn-large {show_topic_button}">New Topic</button>
<div class="inline-block pull-right"> <div class="inline-block pull-right">
@@ -27,7 +22,12 @@
</div> </div>
</div> </div>
<hr class="{show_sidebar}" /> <hr/>
<div class="alert alert-warning hide {no_topics_message}" id="category-no-topics">
<strong>There are no topics in this category.</strong><br />
Why don't you try posting one?
</div>
<div class="category row"> <div class="category row">
<div class="{topic_row_size}"> <div class="{topic_row_size}">

View File

@@ -25,13 +25,17 @@
"users[^]*following": "following", "users[^]*following": "following",
"users[^]*followers": "followers", "users[^]*followers": "followers",
"users[^]*settings": "accountsettings", "users[^]*settings": "accountsettings",
"users[^]*favourites": "favourites",
"users/[^]*": "account", "users/[^]*": "account",
"recent": "recent", "recent": "recent",
"unread": "unread", "unread": "unread",
"popular": "category", "popular": "category",
"active": "category", "active": "category",
"search": "search" "search": "search",
"reset/[^]*": "reset_code",
"reset": "reset"
}, },
"force_refresh": { "force_refresh": {
"logout": true "logout": true

View File

@@ -0,0 +1,34 @@
<div class="well">
<div class="account-username-box" data-userslug="{userslug}">
<span class="account-username">
<a href="/users/{userslug}">{username}</a>
</span>
</div>
<div id="no-favourites-notice" class="alert alert-warning {show_nofavourites}">You don't have any favourites, favourite some posts to see them here!</div>
<div class="row-fluid">
<div class="span12 user-favourite-posts">
<!-- BEGIN posts -->
<div class="topic-row img-polaroid clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
<span><strong>{posts.username}</strong> : </span>
<span>{posts.content}</span>
<div>
<span class="pull-right">{posts.relativeTime} ago</span>
</div>
</div>
<br/>
<!-- END posts -->
</div>
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/favourites.js"></script>

View File

@@ -1,17 +1,11 @@
<div class="well"> <div class="well">
<div class="account-username-box"> <div class="account-username-box" data-userslug="{userslug}">
<span class="account-username"> <span class="account-username">
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i> <a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/followers">followers</a> <a href="/users/{userslug}/followers">followers</a>
</span> </span>
<div class="account-sub-links inline-block pull-right">
<span id="settingsLink" class="pull-right"><a href="/users/{userslug}/settings">settings</a></span>
<span class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<span id="editLink" class="pull-right"><a href="/users/{userslug}/edit">edit</a></span>
</div>
</div> </div>
<div> <div>
@@ -24,15 +18,14 @@
<a href="/users/{followers.userslug}">{followers.username}</a> <a href="/users/{followers.userslug}">{followers.username}</a>
<br/> <br/>
<div title="reputation"> <div title="reputation">
<span class='reputation'>{followers.reputation}</span> <span class='formatted-number'>{followers.reputation}</span>
<i class='icon-star'></i> <i class='icon-star'></i>
</div> </div>
<div title="post count"> <div title="post count">
<span class='postcount'>{followers.postcount}</span> <span class='formatted-number'>{followers.postcount}</span>
<i class='icon-pencil'></i> <i class='icon-pencil'></i>
</div> </div>
</div> </div>
<!-- END followers --> <!-- END followers -->
</div> </div>
<div id="no-followers-notice" class="alert alert-warning hide">This user doesn't have any followers :(</div> <div id="no-followers-notice" class="alert alert-warning hide">This user doesn't have any followers :(</div>

View File

@@ -1,19 +1,11 @@
<div class="well"> <div class="well">
<div class="account-username-box" data-userslug="{userslug}">
<div class="account-username-box">
<span class="account-username"> <span class="account-username">
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i> <a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/following">following</a> <a href="/users/{userslug}/following">following</a>
</span> </span>
<div class="account-sub-links inline-block pull-right">
<span id="settingsLink" class="pull-right"><a href="/users/{userslug}/settings">settings</a></span>
<span class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<span id="editLink" class="pull-right"><a href="/users/{userslug}/edit">edit</a></span>
</div>
</div> </div>
<div> <div>
@@ -26,11 +18,11 @@
<a href="/users/{following.userslug}">{following.username}</a> <a href="/users/{following.userslug}">{following.username}</a>
<br/> <br/>
<div title="reputation"> <div title="reputation">
<span class='reputation'>{following.reputation}</span> <span class='formatted-number'>{following.reputation}</span>
<i class='icon-star'></i> <i class='icon-star'></i>
</div> </div>
<div title="post count"> <div title="post count">
<span class='postcount'>{following.postcount}</span> <span class='formatted-number'>{following.postcount}</span>
<i class='icon-pencil'></i> <i class='icon-pencil'></i>
</div> </div>
<a id="unfollow-btn" href="#" class="btn unfollow-btn" followingUid="{following.uid}" data-username="{following.username}">Unfollow</a> <a id="unfollow-btn" href="#" class="btn unfollow-btn" followingUid="{following.uid}" data-username="{following.username}">Unfollow</a>

View File

@@ -48,8 +48,8 @@
<li> <li>
<a href="/recent">Recent</a> <a href="/recent">Recent</a>
</li> </li>
<li> <li class="nodebb-loggedin">
<a href="/unread">Unread</a> <a href="/unread"><span id="numUnreadBadge" class="badge badge-inverse">0</span> Unread</a>
</li> </li>
<li> <li>
<a href="/users">Users</a> <a href="/users">Users</a>
@@ -62,11 +62,12 @@
<li><a href="#"><i class="icon-refresh icon-spin"></i> Loading Notifications</a></li> <li><a href="#"><i class="icon-refresh icon-spin"></i> Loading Notifications</a></li>
</ul> </ul>
</li> </li>
<form id="search-form" <li>
class="form-search form-inline" action="" method="GET"> <form id="search-form" class="form-search form-inline" action="" method="GET">
<input type="text" name="query" class="input-medium search-query"> <input type="text" name="query" class="input-medium search-query">
<button type="submit" class="btn hide">Search</button> <button type="submit" class="btn hide">Search</button>
</form> </form>
</li>
</ul> </ul>
</div> </div>

View File

@@ -110,7 +110,6 @@
contentEl.addEventListener('click', function(e) { contentEl.addEventListener('click', function(e) {
if (e.target.hasAttribute('data-path')) { if (e.target.hasAttribute('data-path')) {
var href = 'install/' + e.target.getAttribute('data-path'); var href = 'install/' + e.target.getAttribute('data-path');
console.log(href);
if (!e.target.disabled) ajaxify.go(href); if (!e.target.disabled) ajaxify.go(href);
} }
}, false); }, false);

View File

@@ -14,7 +14,7 @@
<button class="btn btn-primary" id="login" type="submit">Login</button> &nbsp; <a href="/reset">Forgot Password?</a> <button class="btn btn-primary" id="login" type="submit">Login</button> &nbsp; <a href="/reset">Forgot Password?</a>
</form> </form>
<span id="login-error-notify" class="label label-important hide">Invalid username/password</span><br/> <div id="login-error-notify" class="alert alert-danger hide">Invalid username/password</div>
</div> </div>
<div class="well span6 {alternate_logins:display}"> <div class="well span6 {alternate_logins:display}">

View File

@@ -42,7 +42,7 @@
<button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn delete {main_posts.display_moderator_tools}" type="button" title="Delete"><i class="icon-trash"></i></button> <button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn delete {main_posts.display_moderator_tools}" type="button" title="Delete"><i class="icon-trash"></i></button>
<div class="btn-group"> <div class="btn-group">
<button class="btn follow" type="button" title="Be notified of new replies in this topic"><i class="icon-eye-open"></i></button> <button class="btn follow" type="button" title="Be notified of new replies in this topic"><i class="icon-eye-open"></i></button>
<button id="favs_{main_posts.pid}_{main_posts.uid}" class="favourite btn" type="button"> <button id="favs_{main_posts.pid}_{main_posts.uid}" class="favourite btn {main_posts.fav_button_class}" type="button">
<span>Favourite</span> <span>Favourite</span>
<span class="post_rep_{main_posts.pid}">{main_posts.post_rep} </span><i class="{main_posts.fav_star_class}"></i> <span class="post_rep_{main_posts.pid}">{main_posts.post_rep} </span><i class="{main_posts.fav_star_class}"></i>
</button> </button>
@@ -89,9 +89,10 @@
<i class="icon-star"></i><span class="user_rep_{posts.uid} formatted-number">{posts.user_rep}</span> <i class="icon-star"></i><span class="user_rep_{posts.uid} formatted-number">{posts.user_rep}</span>
<div id="ids_{posts.pid}_{posts.uid}" class="chat hidden-phone" title="Chat"><i class="icon-comment"></i></div> <div id="ids_{posts.pid}_{posts.uid}" class="chat hidden-phone" title="Chat"><i class="icon-comment"></i></div>
</div> </div>
<span class="label label-important {posts.show_banned}">banned</span>
</div> </div>
<div class="span11 span12-tablet"> <div class="span11 span12-tablet">
<div class="post-block"> <div class="post-block speech-bubble">
<div id="content_{posts.pid}" class="post-content">{posts.content}</div> <div id="content_{posts.pid}" class="post-content">{posts.content}</div>
<div id="images_{posts.pid}" class="post-images"> <div id="images_{posts.pid}" class="post-images">
<!-- BEGIN uploadedImages --> <!-- BEGIN uploadedImages -->

View File

@@ -16,7 +16,7 @@
</div> </div>
<div> <div>
<button id="mark-allread-btn" class="btn {show_markallread_button}">Mark All Read</button> <button id="mark-allread-btn" class="btn {show_markallread_button}">Mark All As Read</button>
</div> </div>
<div class="category row"> <div class="category row">

View File

@@ -24,11 +24,11 @@
<a href="/users/{users.userslug}">{users.username}</a> <a href="/users/{users.userslug}">{users.username}</a>
<br/> <br/>
<div title="reputation"> <div title="reputation">
<span class='reputation'>{users.reputation}</span> <span class='formatted-number'>{users.reputation}</span>
<i class='icon-star'></i> <i class='icon-star'></i>
</div> </div>
<div title="post count"> <div title="post count">
<span class='postcount'>{users.postcount}</span> <span class='formatted-number'>{users.postcount}</span>
<i class='icon-pencil'></i> <i class='icon-pencil'></i>
</div> </div>
</div> </div>

View File

@@ -45,7 +45,7 @@ var RDB = require('./../redis.js'),
UserAdmin.deleteUser = function(uid, theirid, socket) { UserAdmin.deleteUser = function(uid, theirid, socket) {
user.isAdministrator(uid, function(amIAdmin) { user.isAdministrator(uid, function(amIAdmin) {
user.isAdministrator(theirid, function(areTheyAdmin){ user.isAdministrator(theirid, function(areTheyAdmin) {
if(amIAdmin && !areTheyAdmin) { if(amIAdmin && !areTheyAdmin) {
user.delete(theirid, function(data) { user.delete(theirid, function(data) {
@@ -58,8 +58,39 @@ var RDB = require('./../redis.js'),
}); });
} }
}); });
});
};
UserAdmin.banUser = function(uid, theirid, socket) {
user.isAdministrator(uid, function(amIAdmin) {
user.isAdministrator(theirid, function(areTheyAdmin) {
if(amIAdmin && !areTheyAdmin) {
user.ban(theirid, function(err, result) {
socket.emit('event:alert', {
title: 'User Banned',
message: 'This user is banned!',
type: 'success',
timeout: 2000
});
});
}
});
});
};
UserAdmin.unbanUser = function(uid, theirid, socket) {
user.isAdministrator(uid, function(amIAdmin) {
if(amIAdmin) {
user.unban(theirid, function(err, result) {
socket.emit('event:alert', {
title: 'User Unbanned',
message: 'This user is unbanned!',
type: 'success',
timeout: 2000
});
});
}
}); });
}; };

View File

@@ -3,12 +3,13 @@ var RDB = require('./redis.js'),
utils = require('./../public/src/utils.js'), utils = require('./../public/src/utils.js'),
user = require('./user.js'), user = require('./user.js'),
async = require('async'), async = require('async'),
topics = require('./topics.js'); topics = require('./topics.js'),
winston = require('winston');
(function(Categories) { (function(Categories) {
Categories.getCategoryById = function(category_id, current_user, callback) { Categories.getCategoryById = function(category_id, current_user, callback) {
Categories.getCategoryData(category_id, function(err, categoryData) { Categories.getCategoryData(category_id, function(err, categoryData) {
if (err) return callback(err); if (err) return callback(err);
@@ -38,9 +39,9 @@ var RDB = require('./redis.js'),
'category_id': category_id, 'category_id': category_id,
'active_users': [], 'active_users': [],
'topics' : [], 'topics' : [],
'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(global.nconf.get('url') + 'category/' + category_slug) + '&text=' + encodeURIComponent(category_name), 'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug) + '&text=' + encodeURIComponent(category_name),
'facebook-share-url': 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(global.nconf.get('url') + 'category/' + category_slug), 'facebook-share-url': 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug),
'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(global.nconf.get('url') + 'category/' + category_slug) 'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug)
}; };
function getTopics(next) { function getTopics(next) {
@@ -48,7 +49,7 @@ var RDB = require('./redis.js'),
next(null, topicsData); next(null, topicsData);
}, category_id); }, category_id);
} }
function getModerators(next) { function getModerators(next) {
Categories.getModerators(category_id, next); Categories.getModerators(category_id, next);
} }
@@ -65,7 +66,6 @@ var RDB = require('./redis.js'),
categoryData.moderators = moderators; categoryData.moderators = moderators;
categoryData.show_sidebar = 'hidden'; categoryData.show_sidebar = 'hidden';
categoryData.no_topics_message = 'show'; categoryData.no_topics_message = 'show';
callback(null, categoryData); callback(null, categoryData);
}); });
} else { } else {
@@ -156,22 +156,22 @@ var RDB = require('./redis.js'),
break; break;
} }
} }
callback(allread); callback(allread);
}); });
}); });
} }
Categories.markAsRead = function(cid, uid) { Categories.markAsRead = function(cid, uid) {
RDB.sadd('cid:' + cid + ':read_by_uid', uid); RDB.sadd('cid:' + cid + ':read_by_uid', uid);
} }
Categories.hasReadCategories = function(cids, uid, callback) { Categories.hasReadCategories = function(cids, uid, callback) {
var batch = RDB.multi(); var batch = RDB.multi();
for (var i=0, ii=cids.length; i<ii; i++) { for (var i=0, ii=cids.length; i<ii; i++) {
batch.sismember('cid:' + cids[i] + ':read_by_uid', uid); batch.sismember('cid:' + cids[i] + ':read_by_uid', uid);
} }
batch.exec(function(err, hasRead) { batch.exec(function(err, hasRead) {
callback(hasRead); callback(hasRead);
}); });
@@ -180,15 +180,16 @@ var RDB = require('./redis.js'),
Categories.hasReadCategory = function(cid, uid, callback) { Categories.hasReadCategory = function(cid, uid, callback) {
RDB.sismember('cid:' + cid + ':read_by_uid', uid, function(err, hasRead) { RDB.sismember('cid:' + cid + ':read_by_uid', uid, function(err, hasRead) {
RDB.handle(err); RDB.handle(err);
callback(hasRead); callback(hasRead);
}); });
} }
Categories.getRecentReplies = function(cid, count, callback) { Categories.getRecentReplies = function(cid, count, callback) {
RDB.zrevrange('categories:recent_posts:cid:' + cid, 0, (count<10)?10:count, function(err, pids) { RDB.zrevrange('categories:recent_posts:cid:' + cid, 0, (count<10)?10:count, function(err, pids) {
if(err) { if(err) {
console.log(err); winston.err(err);
callback([]); callback([]);
return; return;
} }
@@ -198,11 +199,11 @@ var RDB = require('./redis.js'),
return; return;
} }
posts.getPostSummaryByPids(pids, function(posts) { posts.getPostSummaryByPids(pids, function(err, postData) {
if(posts.length > count) { if(postData.length > count) {
posts = posts.slice(0, count); postData = postData.slice(0, count);
} }
callback(posts); callback(postData);
}); });
}); });
} }
@@ -213,8 +214,8 @@ var RDB = require('./redis.js'),
function movePost(pid, callback) { function movePost(pid, callback) {
posts.getPostField(pid, 'timestamp', function(timestamp) { posts.getPostField(pid, 'timestamp', function(timestamp) {
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid); RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid); RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
}); });
} }
@@ -222,12 +223,12 @@ var RDB = require('./redis.js'),
if(!err) { if(!err) {
callback(null, 1) callback(null, 1)
} else { } else {
console.log(err); winston.err(err);
callback(err, null); callback(err, null);
} }
}); });
} else { } else {
console.log(err); winston.err(err);
callback(err, null); callback(err, null);
} }
}); });
@@ -239,20 +240,20 @@ var RDB = require('./redis.js'),
else callback(new Error('No category found!')); else callback(new Error('No category found!'));
}); });
} }
Categories.getCategoryField = function(cid, field, callback) { Categories.getCategoryField = function(cid, field, callback) {
RDB.hget('category:' + cid, field, callback); RDB.hget('category:' + cid, field, callback);
} }
Categories.getCategoryFields = function(cid, fields, callback) { Categories.getCategoryFields = function(cid, fields, callback) {
RDB.hmgetObject('category:' + cid, fields, function(err, data) { RDB.hmgetObject('category:' + cid, fields, function(err, data) {
if(err === null) if(err === null)
callback(data); callback(data);
else else
console.log(err); winston.err(err);
}); });
} }
Categories.setCategoryField = function(cid, field, value) { Categories.setCategoryField = function(cid, field, value) {
RDB.hset('category:' + cid, field, value); RDB.hset('category:' + cid, field, value);
} }
@@ -266,37 +267,37 @@ var RDB = require('./redis.js'),
callback({'categories' : []}); callback({'categories' : []});
return; return;
} }
var categories = []; var categories = [];
function getCategory(cid, callback) { function getCategory(cid, callback) {
Categories.getCategoryData(cid, function(err, categoryData) { Categories.getCategoryData(cid, function(err, categoryData) {
if(err) { if(err) {
callback(err); callback(err);
return; return;
} }
Categories.hasReadCategory(cid, current_user, function(hasRead) { Categories.hasReadCategory(cid, current_user, function(hasRead) {
categoryData['badgeclass'] = (parseInt(categoryData.topic_count,10) === 0 || (hasRead && current_user != 0)) ? '' : 'badge-important'; categoryData['badgeclass'] = (parseInt(categoryData.topic_count, 10) === 0 || (hasRead && current_user != 0)) ? '' : 'badge-important';
categories.push(categoryData); categories.push(categoryData);
callback(null); callback(null);
}) ; }) ;
}); });
} }
async.eachSeries(cids, getCategory, function(err) { async.eachSeries(cids, getCategory, function(err) {
if(err) { if(err) {
console.log(err); winston.err(err);
callback(null); callback(null);
return; return;
} }
callback({'categories': categories}); callback({'categories': categories});
}); });
}; };
}(exports)); }(exports));

View File

@@ -13,33 +13,31 @@ var RDB = require('./redis.js'),
type: 'error', type: 'error',
timeout: 5000 timeout: 5000
}); });
socket.emit('api:posts.favourite', {
status: 'error',
pid: pid
});
return; return;
} }
posts.getPostField(pid, 'uid', function(uid_of_poster) { posts.getPostFields(pid, ['uid', 'timestamp'], function(postData) {
Favourites.hasFavourited(pid, uid, function(hasFavourited) { Favourites.hasFavourited(pid, uid, function(hasFavourited) {
if (hasFavourited == false) { if (hasFavourited == false) {
RDB.sadd('pid:' + pid + ':users_favourited', uid); RDB.sadd('pid:' + pid + ':users_favourited', uid);
RDB.zadd('uid:' + uid + ':favourites', postData.timestamp, pid);
RDB.hincrby('post:' + pid, 'reputation', 1); RDB.hincrby('post:' + pid, 'reputation', 1);
if (uid !== uid_of_poster) { if (uid !== postData.uid) {
user.incrementUserFieldBy(uid_of_poster, 'reputation', 1, function(err, newreputation) { user.incrementUserFieldBy(postData.uid, 'reputation', 1, function(err, newreputation) {
RDB.zadd('users:reputation', newreputation, uid_of_poster); RDB.zadd('users:reputation', newreputation, postData.uid);
}); });
} }
if (room_id) { if (room_id) {
io.sockets.in(room_id).emit('event:rep_up', {uid: uid !== uid_of_poster ? uid_of_poster : 0, pid: pid}); io.sockets.in(room_id).emit('event:rep_up', {uid: uid !== postData.uid ? postData.uid : 0, pid: pid});
} }
socket.emit('api:posts.favourite', { socket.emit('api:posts.favourite', {
status: 'ok' status: 'ok',
pid: pid
}); });
} }
}); });
@@ -62,10 +60,12 @@ var RDB = require('./redis.js'),
Favourites.hasFavourited(pid, uid, function(hasFavourited) { Favourites.hasFavourited(pid, uid, function(hasFavourited) {
if (hasFavourited == true) { if (hasFavourited == true) {
RDB.srem('pid:' + pid + ':users_favourited', uid); RDB.srem('pid:' + pid + ':users_favourited', uid);
RDB.zrem('uid:' + uid + ':favourites', pid);
RDB.hincrby('post:' + pid, 'reputation', -1); RDB.hincrby('post:' + pid, 'reputation', -1);
if (uid !== uid_of_poster) { if (uid !== uid_of_poster) {
user.incrementUserFieldBy(uid_of_poster, 'reputation', -1, function(err, newreputation) { user.incrementUserFieldBy(uid_of_poster, 'reputation', -1, function(err, newreputation) {
RDB.zadd('users:reputation', newreputation, uid_of_poster); RDB.zadd('users:reputation', newreputation, uid_of_poster);
@@ -75,6 +75,11 @@ var RDB = require('./redis.js'),
if (room_id) { if (room_id) {
io.sockets.in(room_id).emit('event:rep_down', {uid: uid !== uid_of_poster ? uid_of_poster : 0, pid: pid}); io.sockets.in(room_id).emit('event:rep_down', {uid: uid !== uid_of_poster ? uid_of_poster : 0, pid: pid});
} }
socket.emit('api:posts.unfavourite', {
status: 'ok',
pid: pid
});
} }
}); });
}); });
@@ -83,7 +88,7 @@ var RDB = require('./redis.js'),
Favourites.hasFavourited = function(pid, uid, callback) { Favourites.hasFavourited = function(pid, uid, callback) {
RDB.sismember('pid:' + pid + ':users_favourited', uid, function(err, hasFavourited) { RDB.sismember('pid:' + pid + ':users_favourited', uid, function(err, hasFavourited) {
RDB.handle(err); RDB.handle(err);
callback(hasFavourited); callback(hasFavourited);
}); });
} }
@@ -95,10 +100,10 @@ var RDB = require('./redis.js'),
for (var i=0, ii=pids.length; i<ii; i++) { for (var i=0, ii=pids.length; i<ii; i++) {
(function(post_id) { (function(post_id) {
Favourites.hasFavourited(post_id, uid, function(hasFavourited) { Favourites.hasFavourited(post_id, uid, function(hasFavourited) {
data[post_id] = hasFavourited; data[post_id] = hasFavourited;
loaded ++; loaded ++;
if (loaded === pids.length) if (loaded === pids.length)
callback(data); callback(data);
}); });
}(pids[i])); }(pids[i]));

View File

@@ -4,12 +4,13 @@
posts = require('./posts.js'), posts = require('./posts.js'),
topics = require('./topics.js'), topics = require('./topics.js'),
fs = require('fs'), fs = require('fs'),
rss = require('node-rss'); rss = require('node-rss'),
winston = require('winston');
function saveFeed(location, feed) { function saveFeed(location, feed) {
fs.writeFile(location, rss.getFeedXML(feed), function (err) { fs.writeFile(location, rss.getFeedXML(feed), function (err) {
if(err) { if(err) {
console.log(err); winston.err(err);
} }
}); });
} }
@@ -32,7 +33,7 @@
var cache_time_in_seconds = 60; var cache_time_in_seconds = 60;
topics.getTopicWithPosts(tid, 0, function(err, topicData) { topics.getTopicWithPosts(tid, 0, function(err, topicData) {
if (err) console.log('Error: Problem saving topic RSS feed', err); if (err) winston.error('Problem saving topic RSS feed', err.stack);
var location = '/topic/' + topicData.slug, var location = '/topic/' + topicData.slug,
xml_url = '/topic/' + tid + '.rss'; xml_url = '/topic/' + tid + '.rss';
@@ -69,7 +70,7 @@
Feed.updateCategory = function(cid) { Feed.updateCategory = function(cid) {
categories.getCategoryById(cid, 0, function(err, categoryData) { categories.getCategoryById(cid, 0, function(err, categoryData) {
if (err) { if (err) {
console.log('Error: Could not update RSS feed for category ' + cid); winston.error('Could not update RSS feed for category ' + cid, err.stack);
return; return;
} }

View File

@@ -14,7 +14,7 @@ var request = require('request');
} }
}; };
var post = request.post(options, function(err, req, body){ var post = request.post(options, function(err, req, body) {
try{ try{
callback(err, JSON.parse(body)); callback(err, JSON.parse(body));
} catch(e) { } catch(e) {

View File

@@ -79,16 +79,16 @@ var async = require('async'),
port: config.port port: config.port
}, },
api_url: protocol + '//' + host + (config.use_port ? ':' + config.port : '') + relative_path + '/api/', api_url: protocol + '//' + host + (config.use_port ? ':' + config.port : '') + relative_path + '/api/',
relative_path: relative_path relative_path: relative_path
}; };
server_conf.base_url = protocol + '//' + host; server_conf.base_url = protocol + '//' + host;
server_conf.relative_path = relative_path; server_conf.relative_path = relative_path;
server_conf.imgurClientID = '';
meta.configs.set('postDelay', 10000);
meta.config.set('postDelay', 10000); meta.configs.set('minimumPostLength', 8);
meta.config.set('minimumPostLength', 8); meta.configs.set('minimumTitleLength', 3);
meta.config.set('minimumTitleLength', 3); meta.configs.set('imgurClientID', '');
install.save(server_conf, client_conf, callback); install.save(server_conf, client_conf, callback);
}); });

View File

@@ -2,7 +2,8 @@
var user = require('./user.js'), var user = require('./user.js'),
bcrypt = require('bcrypt'), bcrypt = require('bcrypt'),
RDB = require('./redis.js'), RDB = require('./redis.js'),
path = require('path'); path = require('path'),
winston = require('winston');
(function(Login){ (function(Login){
@@ -23,18 +24,28 @@ var user = require('./user.js'),
message: 'invalid-user' message: 'invalid-user'
}); });
} }
user.getUserField(uid, 'password', function(user_password) { user.getUserFields(uid, ['password', 'banned'], function(err, userData) {
bcrypt.compare(password, user_password, function(err, res) { if(err)
return next(err);
if(userData.banned && userData.banned === '1') {
return next({
status: "error",
message: "user-banned"
});
}
bcrypt.compare(password, userData.password, function(err, res) {
if(err) { if(err) {
console.log(err); winston.err(err);
next({ next({
status: "error", status: "error",
message: 'bcrypt compare error' message: 'bcrypt compare error'
}); });
return; return;
} }
if (res) { if (res) {
next({ next({
status: "ok", status: "ok",

76
src/messaging.js Normal file
View File

@@ -0,0 +1,76 @@
var RDB = require('./redis'),
async = require('async');
(function(Messaging) {
function sortUids(fromuid, touid) {
var uids = [fromuid, touid];
uids.sort();
return uids;
}
Messaging.addMessage = function(fromuid, touid, content, callback) {
var uids = sortUids(fromuid, touid);
RDB.incr('global:next_message_id', function(err, mid) {
if(err)
return callback(err, null);
var message = {
content: content,
timestamp: Date.now(),
fromuid: fromuid,
touid: touid
};
RDB.hmset('message:' + mid, message);
RDB.rpush('messages:' + uids[0] + ':' + uids[1], mid);
callback(null, message);
});
}
Messaging.getMessages = function(fromuid, touid, callback) {
var uids = sortUids(fromuid, touid);
RDB.lrange('messages:' + uids[0] + ':' + uids[1], 0, -1, function(err, mids) {
if(err)
return callback(err, null);
if(!mids || !mids.length) {
return callback(null, []);
}
user.getUserField(touid, 'username', function(err, tousername) {
var messages = [];
function getMessage(mid, next) {
RDB.hgetall('message:' + mid, function(err, message) {
if(err)
return next(err);
if(message.fromuid === fromuid)
message.content = 'You : ' + message.content;
else
message.content = tousername + ' : ' + message.content;
messages.push(message);
next(null);
});
}
async.eachSeries(mids, getMessage, function(err) {
if(err)
return callback(err, null);
callback(null, messages);
});
});
});
}
}(exports));

View File

@@ -5,7 +5,14 @@ var utils = require('./../public/src/utils.js'),
fs = require('fs'); fs = require('fs');
(function(Meta) { (function(Meta) {
Meta.config = {
Meta.configs = {
init: function(callback) {
Meta.configs.get(function(config) {
Meta.config = config;
callback();
});
},
get: function(callback) { get: function(callback) {
RDB.hgetall('config', function(err, config) { RDB.hgetall('config', function(err, config) {
if (!err) { if (!err) {
@@ -48,9 +55,9 @@ var utils = require('./../public/src/utils.js'),
if (exists) { if (exists) {
fs.readFile(themeConfPath, function(err, conf) { fs.readFile(themeConfPath, function(err, conf) {
conf = JSON.parse(conf); conf = JSON.parse(conf);
conf.src = global.nconf.get('url') + 'themes/' + themeDir + '/' + conf.src; conf.src = nconf.get('url') + 'themes/' + themeDir + '/' + conf.src;
if (conf.screenshot) conf.screenshot = global.nconf.get('url') + 'themes/' + themeDir + '/' + conf.screenshot; if (conf.screenshot) conf.screenshot = nconf.get('url') + 'themes/' + themeDir + '/' + conf.screenshot;
else conf.screenshot = global.nconf.get('url') + 'images/themes/default.png'; else conf.screenshot = nconf.get('url') + 'images/themes/default.png';
themeArr.push(conf); themeArr.push(conf);
next(); next();
}); });
@@ -80,10 +87,10 @@ var utils = require('./../public/src/utils.js'),
}, function(err, values) { }, function(err, values) {
var title; var title;
if (err) title = global.config.title || 'NodeBB'; if (err) title = Meta.config.title || 'NodeBB';
else title = (values.notifCount > 0 ? '(' + values.notifCount + ') ' : '') + (values.title ? values.title + ' | ' : '') + (global.config.title || 'NodeBB'); else title = (values.title ? values.title + ' | ' : '') + (Meta.config.title || 'NodeBB');
callback(null, title); callback(null, title, values.notifCount);
}); });
}, },
parseFragment: function(urlFragment, callback) { parseFragment: function(urlFragment, callback) {
@@ -110,4 +117,7 @@ var utils = require('./../public/src/utils.js'),
} else callback(null); } else callback(null);
} }
} }
}(exports));
}(exports));

View File

@@ -2,12 +2,13 @@ var fs = require('fs'),
path = require('path'), path = require('path'),
RDB = require('./redis.js'), RDB = require('./redis.js'),
async = require('async'), async = require('async'),
winston = require('winston'),
plugins = { plugins = {
libraries: [], libraries: [],
loadedHooks: {}, loadedHooks: {},
init: function() { init: function() {
if (this.initialized) return; if (this.initialized) return;
if (global.env === 'development') console.log('Info: [plugins] Initializing plugins system'); if (global.env === 'development') winston.info('[plugins] Initializing plugins system');
var _self = this; var _self = this;
@@ -19,40 +20,44 @@ var fs = require('fs'),
function(plugins, next) { function(plugins, next) {
async.each(plugins, function(plugin) { async.each(plugins, function(plugin) {
// TODO: Update this check to also check node_modules // TODO: Update this check to also check node_modules
var pluginPath = path.join(__dirname, '../plugins/', plugin); var pluginPath = path.join(__dirname, '../plugins/', plugin),
fs.exists(pluginPath, function(exists) { modulePath = path.join(__dirname, '../node_modules/', plugin);
if (exists) { if (fs.existsSync(pluginPath)) _self.loadPlugin(pluginPath, next);
fs.readFile(path.join(pluginPath, 'plugin.json'), function(err, data) { else if (fs.existsSync(modulePath)) _self.loadPlugin(modulePath, next);
if (err) return next(err); else {
if (global.env === 'development') winston.info('[plugins] Plugin \'' + plugin + '\' not found');
var pluginData = JSON.parse(data); next(); // Ignore this plugin silently
_self.libraries[pluginData.id] = require(path.join(pluginPath, pluginData.library)); }
if (pluginData.hooks) {
for(var x=0,numHooks=pluginData.hooks.length;x<numHooks;x++) {
_self.registerHook(pluginData.id, pluginData.hooks[x]);
}
}
if (global.env === 'development') console.log('Info: [plugins] Loaded plugin: ' + pluginData.id);
next();
});
} else {
if (global.env === 'development') console.log('Info: [plugins] Plugin \'' + plugin + '\' not found');
next(); // Ignore this plugin silently
}
})
}, next); }, next);
} }
], function(err) { ], function(err) {
if (err) { if (err) {
if (global.env === 'development') console.log('Info: [plugins] NodeBB encountered a problem while loading plugins', err.message); if (global.env === 'development') winston.info('[plugins] NodeBB encountered a problem while loading plugins', err.message);
return; return;
} }
if (global.env === 'development') console.log('Info: [plugins] Plugins OK'); if (global.env === 'development') winston.info('[plugins] Plugins OK');
}); });
}, },
initialized: false, initialized: false,
loadPlugin: function(pluginPath, callback) {
var _self = this;
fs.readFile(path.join(pluginPath, 'plugin.json'), function(err, data) {
if (err) return callback(err);
var pluginData = JSON.parse(data);
_self.libraries[pluginData.id] = require(path.join(pluginPath, pluginData.library));
if (pluginData.hooks) {
for(var x=0,numHooks=pluginData.hooks.length;x<numHooks;x++) {
_self.registerHook(pluginData.id, pluginData.hooks[x]);
}
}
if (global.env === 'development') winston.info('[plugins] Loaded plugin: ' + pluginData.id);
callback();
});
},
registerHook: function(id, data) { registerHook: function(id, data) {
/* /*
`data` is an object consisting of (* is required): `data` is an object consisting of (* is required):
@@ -65,8 +70,8 @@ var fs = require('fs'),
if (data.hook && data.method) { if (data.hook && data.method) {
_self.loadedHooks[data.hook] = _self.loadedHooks[data.hook] || []; _self.loadedHooks[data.hook] = _self.loadedHooks[data.hook] || [];
_self.loadedHooks[data.hook].push([id, data.method]); _self.loadedHooks[data.hook].push([id, data.method, !!data.callbacked]);
if (global.env === 'development') console.log('Info: [plugins] Hook registered: ' + data.hook + ' will call ' + id); if (global.env === 'development') winston.info('[plugins] Hook registered: ' + data.hook + ' will call ' + id);
} else return; } else return;
}, },
fireHook: function(hook, args, callback) { fireHook: function(hook, args, callback) {
@@ -75,7 +80,7 @@ var fs = require('fs'),
hookList = this.loadedHooks[hook]; hookList = this.loadedHooks[hook];
if (hookList && Array.isArray(hookList)) { if (hookList && Array.isArray(hookList)) {
if (global.env === 'development') console.log('Info: [plugins] Firing hook: \'' + hook + '\''); if (global.env === 'development') winston.info('[plugins] Firing hook: \'' + hook + '\'');
var hookType = hook.split(':')[0]; var hookType = hook.split(':')[0];
switch(hookType) { switch(hookType) {
case 'filter': case 'filter':
@@ -83,7 +88,7 @@ var fs = require('fs'),
var returnVal = (Array.isArray(args) ? args[0] : args); var returnVal = (Array.isArray(args) ? args[0] : args);
async.each(hookList, function(hookObj, next) { async.each(hookList, function(hookObj, next) {
if (hookObj.callbacked) { if (hookObj[2]) {
_self.libraries[hookObj[0]][hookObj[1]](returnVal, function(err, afterVal) { _self.libraries[hookObj[0]][hookObj[1]](returnVal, function(err, afterVal) {
returnVal = afterVal; returnVal = afterVal;
next(err); next(err);
@@ -94,7 +99,9 @@ var fs = require('fs'),
} }
}, function(err) { }, function(err) {
if (err) { if (err) {
if (global.env === 'development') console.log('Info: [plugins] Problem executing hook: ' + hook); if (global.env === 'development') {
winston.info('[plugins] Problem executing hook: ' + hook);
}
} }
callback(returnVal); callback(returnVal);
@@ -109,7 +116,7 @@ var fs = require('fs'),
) { ) {
_self.libraries[hookObj[0]][hookObj[1]].apply(_self.libraries[hookObj[0]], args); _self.libraries[hookObj[0]][hookObj[1]].apply(_self.libraries[hookObj[0]], args);
} else { } else {
if (global.env === 'development') console.log('Info: [plugins] Expected method \'' + hookObj[1] + '\' in plugin \'' + hookObj[0] + '\' not found, skipping.'); if (global.env === 'development') winston.info('[plugins] Expected method \'' + hookObj[1] + '\' in plugin \'' + hookObj[0] + '\' not found, skipping.');
} }
}); });
break; break;
@@ -129,13 +136,13 @@ var fs = require('fs'),
toggleActive: function(id, callback) { toggleActive: function(id, callback) {
this.isActive(id, function(err, active) { this.isActive(id, function(err, active) {
if (err) { if (err) {
if (global.env === 'development') console.log('Info: [plugins] Could not toggle active state on plugin \'' + id + '\''); if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return; return;
} }
RDB[(active ? 'srem' : 'sadd')]('plugins:active', id, function(err, success) { RDB[(active ? 'srem' : 'sadd')]('plugins:active', id, function(err, success) {
if (err) { if (err) {
if (global.env === 'development') console.log('Info: [plugins] Could not toggle active state on plugin \'' + id + '\''); if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return; return;
} }
@@ -149,26 +156,49 @@ var fs = require('fs'),
showInstalled: function(callback) { showInstalled: function(callback) {
// TODO: Also check /node_modules // TODO: Also check /node_modules
var _self = this; var _self = this;
moduleBasePath = path.join(__dirname, '../plugins'); localPluginPath = path.join(__dirname, '../plugins'),
npmPluginPath = path.join(__dirname, '../node_modules');
async.waterfall([ async.waterfall([
function(next) { function(next) {
fs.readdir(moduleBasePath, next); async.parallel([
function(next) {
fs.readdir(localPluginPath, next);
},
function(next) {
fs.readdir(npmPluginPath, next);
}
], function(err, dirs) {
if (err) return next(err);
dirs[0] = dirs[0].map(function(file) {
return path.join(localPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory()) return true;
else return false;
});
dirs[1] = dirs[1].map(function(file) {
return path.join(npmPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory() && file.substr(npmPluginPath.length+1, 14) === 'nodebb-plugin-') return true;
else return false;
});
next(err, dirs[0].concat(dirs[1]));
});
}, },
function(files, next) { function(files, next) {
var plugins = []; var plugins = [];
async.each(files, function(file, next) { async.each(files, function(file, next) {
var modulePath = path.join(moduleBasePath, file), var configPath;
configPath;
async.waterfall([ async.waterfall([
function(next) { function(next) {
fs.stat(path.join(moduleBasePath, file), next); fs.readFile(path.join(file, 'plugin.json'), next);
},
function(stats, next) {
if (stats.isDirectory()) fs.readFile(path.join(modulePath, 'plugin.json'), next);
else next(new Error('not-a-directory'));
}, },
function(configJSON, next) { function(configJSON, next) {
var config = JSON.parse(configJSON); var config = JSON.parse(configJSON);

View File

@@ -4,12 +4,14 @@ var RDB = require('./redis.js'),
threadTools = require('./threadTools.js'), threadTools = require('./threadTools.js'),
user = require('./user.js'), user = require('./user.js'),
async = require('async'), async = require('async'),
utils = require('../public/src/utils'), utils = require('../public/src/utils'),
plugins = require('./plugins'), plugins = require('./plugins'),
reds = require('reds'), reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'), postSearch = reds.createSearch('nodebbpostsearch'),
topicSearch = reds.createSearch('nodebbtopicsearch'); topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
meta = require('./meta.js');
(function(PostTools) { (function(PostTools) {
PostTools.isMain = function(pid, tid, callback) { PostTools.isMain = function(pid, tid, callback) {
@@ -20,8 +22,8 @@ var RDB = require('./redis.js'),
} }
PostTools.privileges = function(pid, uid, callback) { PostTools.privileges = function(pid, uid, callback) {
//todo: break early if one condition is true //todo: break early if one condition is true
function getThreadPrivileges(next) { function getThreadPrivileges(next) {
posts.getPostField(pid, 'tid', function(tid) { posts.getPostField(pid, 'tid', function(tid) {
threadTools.privileges(tid, uid, function(privileges) { threadTools.privileges(tid, uid, function(privileges) {
@@ -39,8 +41,9 @@ var RDB = require('./redis.js'),
} }
function hasEnoughRep(next) { function hasEnoughRep(next) {
user.getUserField(uid, 'reputation', function(reputation) { user.getUserField(uid, 'reputation', function(err, reputation) {
next(null, reputation >= global.config['privileges:manage_content']); if (err) return next(null, false);
next(null, reputation >= meta.config['privileges:manage_content']);
}); });
} }
@@ -94,7 +97,7 @@ var RDB = require('./redis.js'),
PostTools.delete = function(uid, pid) { PostTools.delete = function(uid, pid) {
var success = function() { var success = function() {
posts.setPostField(pid, 'deleted', 1); posts.setPostField(pid, 'deleted', 1);
postSearch.remove(pid); postSearch.remove(pid);
posts.getPostFields(pid, ['tid', 'uid'], function(postData) { posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
@@ -102,7 +105,7 @@ var RDB = require('./redis.js'),
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) { user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
RDB.zadd('users:postcount', postcount, postData.uid); RDB.zadd('users:postcount', postcount, postData.uid);
}); });
io.sockets.in('topic_' + postData.tid).emit('event:post_deleted', { io.sockets.in('topic_' + postData.tid).emit('event:post_deleted', {
pid: pid pid: pid
}); });
@@ -111,12 +114,12 @@ var RDB = require('./redis.js'),
threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) { threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) {
if (err && err.message === 'no-undeleted-pids-found') { if (err && err.message === 'no-undeleted-pids-found') {
threadTools.delete(postData.tid, -1, function(err) { threadTools.delete(postData.tid, -1, function(err) {
if (err) console.log('Error: Could not delete topic (tid: ' + postData.tid + ')'); if (err) winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
}); });
} else { } else {
posts.getPostField(pid, 'timestamp', function(timestamp) { posts.getPostField(pid, 'timestamp', function(timestamp) {
topics.updateTimestamp(postData.tid, timestamp); topics.updateTimestamp(postData.tid, timestamp);
}); });
} }
}); });
}); });
@@ -140,11 +143,11 @@ var RDB = require('./redis.js'),
io.sockets.in('topic_' + postData.tid).emit('event:post_restored', { io.sockets.in('topic_' + postData.tid).emit('event:post_restored', {
pid: pid pid: pid
}); });
threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) { threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) {
posts.getPostField(pid, 'timestamp', function(timestamp) { posts.getPostField(pid, 'timestamp', function(timestamp) {
topics.updateTimestamp(postData.tid, timestamp); topics.updateTimestamp(postData.tid, timestamp);
}); });
}); });
postSearch.index(postData.content, pid); postSearch.index(postData.content, pid);
@@ -168,18 +171,18 @@ var RDB = require('./redis.js'),
if (md && md.length > 0) { if (md && md.length > 0) {
var parsedContentDOM = cheerio.load(marked(md)); var parsedContentDOM = cheerio.load(marked(md));
var domain = global.nconf.get('url'); var domain = nconf.get('url');
parsedContentDOM('a').each(function() { parsedContentDOM('a').each(function() {
this.attr('rel', 'nofollow'); this.attr('rel', 'nofollow');
var href = this.attr('href'); var href = this.attr('href');
if (href && !href.match(domain)) { if (href && !href.match(domain) && !utils.isRelativeUrl(href)) {
this.attr('href', domain + 'outgoing?url=' + encodeURIComponent(href)); this.attr('href', domain + 'outgoing?url=' + encodeURIComponent(href));
if (!isSignature) this.append(' <i class="icon-external-link"></i>'); if (!isSignature) this.append(' <i class="icon-external-link"></i>');
} }
}); });
html = parsedContentDOM.html(); html = parsedContentDOM.html();
} else { } else {

View File

@@ -10,18 +10,20 @@ var RDB = require('./redis.js'),
async = require('async'), async = require('async'),
plugins = require('./plugins'), plugins = require('./plugins'),
reds = require('reds'), reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'), nconf = require('nconf'),
postSearch = reds.createSearch('nodebbpostsearch'); meta = require('./meta.js'),
winston = require('winston');
(function(Posts) { (function(Posts) {
Posts.getPostsByTid = function(tid, start, end, callback) { Posts.getPostsByTid = function(tid, start, end, callback) {
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) { RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
RDB.handle(err); RDB.handle(err);
if (pids.length) { if (pids.length) {
Posts.getPostsByPids(pids, function(posts) { Posts.getPostsByPids(pids, function(err, posts) {
callback(posts); callback(posts);
}); });
} else { } else {
@@ -29,21 +31,26 @@ var RDB = require('./redis.js'),
} }
}); });
} }
Posts.addUserInfoToPost = function(post, callback) { Posts.addUserInfoToPost = function(post, callback) {
user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature'], function(userData) { user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], function(err, userData) {
if(err)
return callback();
post.username = userData.username || 'anonymous'; post.username = userData.username || 'anonymous';
post.userslug = userData.userslug || ''; post.userslug = userData.userslug || '';
post.user_rep = userData.reputation || 0; post.user_rep = userData.reputation || 0;
post.user_postcount = userData.postcount || 0; post.user_postcount = userData.postcount || 0;
post.picture = userData.picture || require('gravatar').url('', {}, https=global.nconf.get('https')); post.user_banned = userData.banned || '0';
post.picture = userData.picture || require('gravatar').url('', {}, https=nconf.get('https'));
post.signature = postTools.markdownToHTML(userData.signature, true); post.signature = postTools.markdownToHTML(userData.signature, true);
if(post.editor !== '') { if(post.editor !== '') {
user.getUserFields(post.editor, ['username', 'userslug'], function(editorData) { user.getUserFields(post.editor, ['username', 'userslug'], function(err, editorData) {
if(err)
return callback();
post.editorname = editorData.username; post.editorname = editorData.username;
post.editorslug = editorData.userslug; post.editorslug = editorData.userslug;
callback(); callback();
}); });
} else { } else {
@@ -53,9 +60,9 @@ var RDB = require('./redis.js'),
} }
Posts.getPostSummaryByPids = function(pids, callback) { Posts.getPostSummaryByPids = function(pids, callback) {
var returnData = []; var posts = [];
function getPostSummary(pid, callback) { function getPostSummary(pid, callback) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) { Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
if(postData.deleted === '1') { if(postData.deleted === '1') {
@@ -63,33 +70,46 @@ var RDB = require('./redis.js'),
} }
Posts.addUserInfoToPost(postData, function() { Posts.addUserInfoToPost(postData, function() {
topics.getTopicField(postData.tid, 'slug', function(err, topicSlug) { topics.getTopicFields(postData.tid, ['slug', 'deleted'], function(err, topicData) {
if(err)
return callback(err);
if(topicData.deleted === '1')
return callback(null);
if(postData.content) if(postData.content)
postData.content = utils.strip_tags(postTools.markdownToHTML(postData.content)); postData.content = utils.strip_tags(postTools.markdownToHTML(postData.content));
postData.topicSlug = topicSlug; postData.relativeTime = utils.relativeTime(postData.timestamp);
returnData.push(postData); postData.topicSlug = topicData.slug;
posts.push(postData);
callback(null); callback(null);
}); });
}); });
}); });
} }
async.eachSeries(pids, getPostSummary, function(err) { async.eachSeries(pids, getPostSummary, function(err) {
if(!err) { if(!err) {
callback(returnData); callback(null, posts);
} else { } else {
console.log(err); callback(err, null);
} }
}); });
}; };
Posts.filterBannedPosts = function(posts) {
return posts.filter(function(post) {
return post.user_banned === '0';
});
}
Posts.getPostData = function(pid, callback) { Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) { RDB.hgetall('post:' + pid, function(err, data) {
if(err === null) { if(err === null) {
callback(data); plugins.fireHook('filter:post.get', data, function(data) {
callback(data);
});
} }
else else
console.log(err); console.log(err);
@@ -104,7 +124,7 @@ var RDB = require('./redis.js'),
else { else {
console.log(err); console.log(err);
} }
}); });
} }
Posts.getPostField = function(pid, field, callback) { Posts.getPostField = function(pid, field, callback) {
@@ -122,20 +142,25 @@ var RDB = require('./redis.js'),
Posts.getPostsByPids = function(pids, callback) { Posts.getPostsByPids = function(pids, callback) {
var posts = []; var posts = [];
function iterator(pid, callback) { function iterator(pid, callback) {
Posts.getPostData(pid, function(postData) { Posts.getPostData(pid, function(postData) {
if(postData) { if(postData) {
postData.relativeTime = utils.relativeTime(postData.timestamp); postData.relativeTime = utils.relativeTime(postData.timestamp);
postData.post_rep = postData.reputation; postData.post_rep = postData.reputation;
postData['edited-class'] = postData.editor !== '' ? '' : 'none'; postData['edited-class'] = postData.editor !== '' ? '' : 'none';
postData['relativeEditTime'] = postData.edited !== '0' ? utils.relativeTime(postData.edited) : ''; postData['relativeEditTime'] = postData.edited !== '0' ? utils.relativeTime(postData.edited) : '';
postData.content = postTools.markdownToHTML(postData.content); postData.content = postTools.markdownToHTML(postData.content);
if(postData.uploadedImages) { if(postData.uploadedImages) {
postData.uploadedImages = JSON.parse(postData.uploadedImages); try {
postData.uploadedImages = JSON.parse(postData.uploadedImages);
} catch(err) {
postData.uploadedImages = [];
winston.err(err);
}
} else { } else {
postData.uploadedImages = []; postData.uploadedImages = [];
} }
@@ -147,9 +172,9 @@ var RDB = require('./redis.js'),
async.eachSeries(pids, iterator, function(err) { async.eachSeries(pids, iterator, function(err) {
if(!err) { if(!err) {
callback(posts); callback(null, posts);
} else { } else {
callback([]); callback(err, null);
} }
}); });
} }
@@ -173,15 +198,15 @@ var RDB = require('./redis.js'),
type: 'error', type: 'error',
timeout: 2000, timeout: 2000,
title: 'Content too short', title: 'Content too short',
message: "Please enter a longer post. At least " + config.minimumPostLength + " characters.", message: "Please enter a longer post. At least " + meta.config.minimumPostLength + " characters.",
alert_id: 'post_error' alert_id: 'post_error'
}); });
} }
Posts.emitTooManyPostsAlert = function(socket) { Posts.emitTooManyPostsAlert = function(socket) {
socket.emit('event:alert', { socket.emit('event:alert', {
title: 'Too many posts!', title: 'Too many posts!',
message: 'You can only post every '+ config.postDelay/1000 + ' seconds.', message: 'You can only post every '+ meta.config.postDelay/1000 + ' seconds.',
type: 'error', type: 'error',
timeout: 2000 timeout: 2000
}); });
@@ -192,49 +217,36 @@ var RDB = require('./redis.js'),
content = content.trim(); content = content.trim();
} }
if (!content || content.length < config.minimumPostLength) { if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null); callback(new Error('content-too-short'), null);
return; return;
} }
user.getUserField(uid, 'lastposttime', function(lastposttime) { user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(Date.now() - lastposttime < config.postDelay) { if(Date.now() - lastposttime < meta.config.postDelay) {
callback(new Error('too-many-posts'), null); callback(new Error('too-many-posts'), null);
return; return;
} }
Posts.create(uid, tid, content, images, function(postData) { Posts.create(uid, tid, content, images, function(postData) {
if (postData) { if (postData) {
topics.addPostToTopic(tid, postData.pid);
topics.markUnRead(tid); topics.markUnRead(tid);
Posts.get_cid_by_pid(postData.pid, function(cid) { Posts.get_cid_by_pid(postData.pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) { RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
topics.markAsRead(tid, uid); topics.markAsRead(tid, uid);
}); });
}); });
threadTools.notify_followers(tid, uid); threadTools.notify_followers(tid, uid);
postData.content = postTools.markdownToHTML(postData.content); Posts.addUserInfoToPost(postData, function() {
postData.post_rep = 0; var socketData = { posts: [postData] };
postData.relativeTime = utils.relativeTime(postData.timestamp)
postData.fav_star_class = 'icon-star-empty';
postData['edited-class'] = 'none';
postData.uploadedImages = JSON.parse(postData.uploadedImages);
var socketData = {
'posts' : [
postData
]
};
posts.addUserInfoToPost(socketData['posts'][0], function() {
io.sockets.in('topic_' + tid).emit('event:new_post', socketData); io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData); io.sockets.in('recent_posts').emit('event:new_post', socketData);
}); });
callback(null, 'Reply successful'); callback(null, 'Reply successful');
} else { } else {
callback(new Error('reply-error'), null); callback(new Error('reply-error'), null);
@@ -242,19 +254,19 @@ var RDB = require('./redis.js'),
}); });
}); });
}; };
Posts.create = function(uid, tid, content, images, callback) { Posts.create = function(uid, tid, content, images, callback) {
if (uid === null) { if (uid === null) {
callback(null); callback(null);
return; return;
} }
topics.isLocked(tid, function(locked) { topics.isLocked(tid, function(locked) {
if (!locked || locked === '0') { if (!locked || locked === '0') {
RDB.incr('global:next_post_id', function(err, pid) { RDB.incr('global:next_post_id', function(err, pid) {
RDB.handle(err); RDB.handle(err);
plugins.fireHook('filter:save_post_content', content, function(content) { plugins.fireHook('filter:post.save', content, function(content) {
var timestamp = Date.now(), var timestamp = Date.now(),
postData = { postData = {
'pid': pid, 'pid': pid,
@@ -266,16 +278,24 @@ var RDB = require('./redis.js'),
'editor': '', 'editor': '',
'edited': 0, 'edited': 0,
'deleted': 0, 'deleted': 0,
'uploadedImages': '' 'uploadedImages': '[]',
'fav_button_class': '',
'fav_star_class': 'icon-star-empty',
'show_banned': 'hide',
'relativeTime': '0 seconds',
'post_rep': '0',
'edited-class': 'none',
'relativeEditTime': ''
}; };
RDB.hmset('post:' + pid, postData); RDB.hmset('post:' + pid, postData);
topics.addPostToTopic(tid, pid);
topics.increasePostCount(tid); topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp); topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount'); RDB.incr('totalpostcount');
topics.getTopicField(tid, 'cid', function(err, cid) { topics.getTopicField(tid, 'cid', function(err, cid) {
RDB.handle(err); RDB.handle(err);
@@ -292,22 +312,36 @@ var RDB = require('./redis.js'),
RDB.sadd('cid:' + cid + ':active_users', uid); RDB.sadd('cid:' + cid + ':active_users', uid);
}); });
}); });
user.onNewPostMade(uid, tid, pid, timestamp);
uploadPostImages(postData, images, function(err, uploadedImages) { user.onNewPostMade(uid, tid, pid, timestamp);
if(err) {
console.log('Uploading images failed!'); async.parallel({
} else { uploadedImages: function(next) {
postData.uploadedImages = JSON.stringify(uploadedImages); uploadPostImages(postData, images, function(err, uploadedImages) {
Posts.setPostField(pid, 'uploadedImages', postData.uploadedImages); if(err) {
winston.error('Uploading images failed!', err.stack);
next(null, []);
} else {
next(null, uploadedImages);
}
});
},
content: function(next) {
plugins.fireHook('filter:post.get', postData, function(postData) {
postData.content = postTools.markdownToHTML(postData.content, false);
next(null, postData.content);
});
} }
}, function(err, results) {
postData.uploadedImages = results.uploadedImages;
Posts.setPostField(pid, 'uploadedImages', JSON.stringify(postData.uploadedImages));
postData.content = results.content;
callback(postData); callback(postData);
}); });
plugins.fireHook('action:save_post_content', [pid, content]); plugins.fireHook('action:post.save', [postData]);
postSearch.index(content, pid); postSearch.index(content, pid);
}); });
}); });
@@ -319,9 +353,9 @@ var RDB = require('./redis.js'),
function uploadPostImages(postData, images, callback) { function uploadPostImages(postData, images, callback) {
var imgur = require('./imgur'); var imgur = require('./imgur');
imgur.setClientID(global.nconf.get('imgurClientID')); imgur.setClientID(meta.config.imgurClientID);
var uploadedImages = []; var uploadedImages = [];
function uploadImage(image, callback) { function uploadImage(image, callback) {
imgur.upload(image.data, 'base64', function(err, data) { imgur.upload(image.data, 'base64', function(err, data) {
@@ -333,10 +367,11 @@ var RDB = require('./redis.js'),
uploadedImages.push(img); uploadedImages.push(img);
callback(null); callback(null);
} else { } else {
winston.error('Can\'t upload image, did you set imgurClientID?');
callback(data); callback(data);
} }
} }
}); });
} }
if(!images) { if(!images) {
@@ -352,20 +387,20 @@ var RDB = require('./redis.js'),
}); });
} }
} }
Posts.getPostsByUid = function(uid, start, end, callback) { Posts.getPostsByUid = function(uid, start, end, callback) {
user.getPostIds(uid, start, end, function(pids) { user.getPostIds(uid, start, end, function(pids) {
if(pids && pids.length) { if(pids && pids.length) {
Posts.getPostsByPids(pids, function(posts) { Posts.getPostsByPids(pids, function(err, posts) {
callback(posts); callback(posts);
}); });
} }
else else
callback([]); callback([]);
}); });
} }
Posts.getTopicPostStats = function(socket) { Posts.getTopicPostStats = function(socket) {
@@ -373,11 +408,11 @@ var RDB = require('./redis.js'),
if(err === null) { if(err === null) {
var stats = { var stats = {
topics: data[0]?data[0]:0, topics: data[0]?data[0]:0,
posts: data[1]?data[1]:0 posts: data[1]?data[1]:0
}; };
socket.emit('post.stats', stats); socket.emit('post.stats', stats);
} }
else else
console.log(err); console.log(err);
}); });
@@ -407,4 +442,19 @@ var RDB = require('./redis.js'),
}); });
} }
Posts.getFavourites = function(uid, callback) {
RDB.zrevrange('uid:' + uid + ':favourites', 0, -1, function(err, pids) {
if(err)
return callback(err, null);
Posts.getPostSummaryByPids(pids, function(err, posts) {
if(err)
return callback(err, null);
callback(null, posts);
});
});
}
}(exports)); }(exports));

View File

@@ -1,8 +1,9 @@
(function(RedisDB) { (function(RedisDB) {
var redis = require('redis'), var redis = require('redis'),
nconf = require('nconf'), nconf = require('nconf'),
utils = require('./../public/src/utils.js'); utils = require('./../public/src/utils.js'),
winston = require('winston');
RedisDB.exports = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')); RedisDB.exports = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'));
if(nconf.get('redis:password')) { if(nconf.get('redis:password')) {
@@ -11,14 +12,9 @@
RedisDB.exports.handle = function(error) { RedisDB.exports.handle = function(error) {
if (error !== null) { if (error !== null) {
winston.err(error);
if (global.env !== 'production') { if (global.env !== 'production') {
console.log("################# ERROR LOG ####################");
console.log(error);
console.log(arguments.callee.name);
console.log("################# ERROR LOG ####################");
throw new Error(error); throw new Error(error);
} else {
console.log(error);
} }
} }
} }
@@ -44,7 +40,7 @@
RedisDB.exports.hmget(key, fields, function(err, data) { RedisDB.exports.hmget(key, fields, function(err, data) {
if(err === null) { if(err === null) {
var returnData = {}; var returnData = {};
for(var i=0, ii=fields.length; i<ii; ++i) { for(var i=0, ii=fields.length; i<ii; ++i) {
returnData[fields[i]] = data[i]; returnData[fields[i]] = data[i];
} }
@@ -55,8 +51,8 @@
console.log(err); console.log(err);
callback(err, null); callback(err, null);
} }
}); });
} }

View File

@@ -4,7 +4,8 @@ var user = require('./../user.js'),
RDB = require('./../redis.js'), RDB = require('./../redis.js'),
pkg = require('./../../package.json'), pkg = require('./../../package.json'),
categories = require('./../categories.js'), categories = require('./../categories.js'),
plugins = require('../plugins'); plugins = require('../plugins'),
winston = require('winston');
(function(Admin) { (function(Admin) {
Admin.isAdmin = function(req, res, next) { Admin.isAdmin = function(req, res, next) {
@@ -17,7 +18,7 @@ var user = require('./../user.js'),
Admin.build_header = function(res) { Admin.build_header = function(res) {
return templates['admin/header'].parse({ return templates['admin/header'].parse({
csrf:res.locals.csrf_token, csrf:res.locals.csrf_token,
relative_path: global.nconf.get('relative_path') relative_path: nconf.get('relative_path')
}); });
} }
@@ -142,7 +143,7 @@ var user = require('./../user.js'),
finalData[key] = jsonObject[key]; finalData[key] = jsonObject[key];
} }
} catch(err){ } catch(err){
console.log('invalid redis status', i, data[i], err); winston.warn('can\'t parse redis status variable, ignoring', i, data[i], err);
} }
} }

View File

@@ -5,7 +5,7 @@ var user = require('./../user.js'),
utils = require('./../../public/src/utils.js'), utils = require('./../../public/src/utils.js'),
pkg = require('../../package.json'), pkg = require('../../package.json'),
meta = require('./../meta.js'); meta = require('./../meta.js');
(function(Api) { (function(Api) {
Api.create_routes = function(app) { Api.create_routes = function(app) {
@@ -16,18 +16,16 @@ var user = require('./../user.js'),
}); });
app.get('/api/config', function(req, res, next) { app.get('/api/config', function(req, res, next) {
meta.config.getFields(['postDelay', 'minimumTitleLength', 'minimumPostLength'], function(err, metaConfig) { var config = require('../../public/config.json');
if(err) return next();
var clientConfig = require('../../public/config.json');
for (var attrname in metaConfig) {
clientConfig[attrname] = metaConfig[attrname];
}
res.json(200, clientConfig); config['postDelay'] = meta.config['postDelay'];
}) config['minimumTitleLength'] = meta.config['minimumTitleLength'];
config['minimumPostLength'] = meta.config['minimumPostLength'];
config['imgurClientIDSet'] = !!meta.config['imgurClientID'];
res.json(200, config);
}); });
app.get('/api/home', function(req, res) { app.get('/api/home', function(req, res) {
var uid = (req.user) ? req.user.uid : 0; var uid = (req.user) ? req.user.uid : 0;
categories.getAllCategories(function(data) { categories.getAllCategories(function(data) {
@@ -45,8 +43,8 @@ var user = require('./../user.js'),
} }
require('async').each(data.categories, iterator, function(err) { require('async').each(data.categories, iterator, function(err) {
data.motd_class = (config.show_motd === '1' || config.show_motd === undefined) ? '' : 'none'; data.motd_class = (meta.config.show_motd === '1' || meta.config.show_motd === undefined) ? '' : 'none';
data.motd = marked(config.motd || "# NodeBB v" + pkg.version + "\nWelcome to NodeBB, the discussion platform of the future.\n\n<a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-large\"><i class=\"icon-comment\"></i> Get NodeBB</a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-large\"><i class=\"icon-github-alt\"></i> Fork us on Github</a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-large\"><i class=\"icon-twitter\"></i> @dcplabs</a>"); data.motd = marked(meta.config.motd || "# NodeBB <span class='hidden-phone'>v " + pkg.version + "</span>\nWelcome to NodeBB, the discussion platform of the future.\n\n<a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-large\"><i class=\"icon-comment\"></i><span class='hidden-phone'>&nbsp;Get NodeBB</span></a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-large\"><i class=\"icon-github-alt\"></i><span class='hidden-phone'>&nbsp;Fork us on Github</span></a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-large\"><i class=\"icon-twitter\"></i><span class='hidden-phone'>&nbsp;@dcplabs</span></a>");
res.json(data); res.json(data);
}); });
@@ -62,7 +60,7 @@ var user = require('./../user.js'),
data = { data = {
'login_window:spansize': 'span12', 'login_window:spansize': 'span12',
'alternate_logins:display': 'none' 'alternate_logins:display': 'none'
}; };
} else { } else {
data = { data = {
'login_window:spansize': 'span6', 'login_window:spansize': 'span6',
@@ -77,7 +75,7 @@ var user = require('./../user.js'),
res.json(data); res.json(data);
}); });
app.get('/api/register', function(req, res) { app.get('/api/register', function(req, res) {
var data = {}, var data = {},
login_strategies = auth.get_login_strategies(), login_strategies = auth.get_login_strategies(),
@@ -87,7 +85,7 @@ var user = require('./../user.js'),
data = { data = {
'register_window:spansize': 'span12', 'register_window:spansize': 'span12',
'alternate_logins:display': 'none' 'alternate_logins:display': 'none'
}; };
} else { } else {
data = { data = {
'register_window:spansize': 'span6', 'register_window:spansize': 'span6',
@@ -102,22 +100,25 @@ var user = require('./../user.js'),
res.json(data); res.json(data);
}); });
app.get('/api/topic/:id/:slug?', function(req, res) { app.get('/api/topic/:id/:slug?', function(req, res, next) {
var uid = (req.user) ? req.user.uid : 0; var uid = (req.user) ? req.user.uid : 0;
topics.getTopicWithPosts(req.params.id, uid, function(err, data) { topics.getTopicWithPosts(req.params.id, uid, function(err, data) {
if(data.deleted === '1' && data.expose_tools === 0) {
return res.json(404, {});
}
res.json(data); res.json(data);
}); });
}); });
app.get('/api/category/:id/:slug?', function(req, res, next) { app.get('/api/category/:id/:slug?', function(req, res, next) {
var uid = (req.user) ? req.user.uid : 0; var uid = (req.user) ? req.user.uid : 0;
categories.getCategoryById(req.params.id, uid, function(err, data) { categories.getCategoryById(req.params.id, uid, function(err, data) {
if (!err) if (!err)
res.json(data); res.json(data);
else else
next(); next();
}, req.params.id, uid); }, req.params.id, uid);
}); });
app.get('/api/recent', function(req, res) { app.get('/api/recent', function(req, res) {
@@ -134,6 +135,13 @@ var user = require('./../user.js'),
}); });
}); });
app.get('/api/unread/total', function(req, res) {
var uid = (req.user) ? req.user.uid : 0;
topics.getTotalUnread(uid, function(data) {
res.json(data);
});
});
app.get('/api/confirm/:id', function(req, res) { app.get('/api/confirm/:id', function(req, res) {
user.email.confirm(req.params.id, function(data) { user.email.confirm(req.params.id, function(data) {
if (data.status === 'ok') { if (data.status === 'ok') {
@@ -158,13 +166,13 @@ var user = require('./../user.js'),
if (url) { if (url) {
res.json({ res.json({
url: url, url: url,
home: global.nconf.get('url') home: nconf.get('url')
}); });
} else { } else {
res.status(404); res.status(404);
res.redirect(global.nconf.get('relative_path') + '/404'); res.redirect(nconf.get('relative_path') + '/404');
} }
}); });
app.get('/api/search', function(req, res) { app.get('/api/search', function(req, res) {
return res.json({ return res.json({
@@ -191,7 +199,9 @@ var user = require('./../user.js'),
if(err) if(err)
return callback(err, null); return callback(err, null);
posts.getPostSummaryByPids(pids, function(posts) { posts.getPostSummaryByPids(pids, function(err, posts) {
if(err)
return callback(err, null);
callback(null, posts); callback(null, posts);
}); });
}) })
@@ -201,7 +211,7 @@ var user = require('./../user.js'),
search(topicSearch, function(err, tids) { search(topicSearch, function(err, tids) {
if(err) if(err)
return callback(err, null); return callback(err, null);
console.log(tids);
topics.getTopicsByTids(tids, 0, function(topics) { topics.getTopicsByTids(tids, 0, function(topics) {
callback(null, topics); callback(null, topics);
}, 0); }, 0);
@@ -209,7 +219,7 @@ var user = require('./../user.js'),
} }
async.parallel([searchPosts, searchTopics], function(err, results) { async.parallel([searchPosts, searchTopics], function(err, results) {
if (err) if (err)
return next(); return next();
var noresults = !results[0].length && !results[1].length; var noresults = !results[0].length && !results[1].length;
return res.json({ return res.json({
@@ -220,11 +230,19 @@ var user = require('./../user.js'),
}); });
}); });
}); });
app.get('/api/reset', function(req, res) {
res.json({});
});
app.get('/api/reset/:code', function(req, res) {
res.json({ reset_code: req.params.code });
});
app.get('/api/404', function(req, res) { app.get('/api/404', function(req, res) {
res.json({}); res.json({});
}); });
app.get('/api/403', function(req, res) { app.get('/api/403', function(req, res) {
res.json({}); res.json({});
}); });

View File

@@ -6,7 +6,9 @@
passportFacebook = require('passport-facebook').Strategy, passportFacebook = require('passport-facebook').Strategy,
login_strategies = [], login_strategies = [],
nconf = require('nconf'), nconf = require('nconf'),
users = require('../user'), meta = require('../meta'),
user = require('../user'),
winston = require('winston'),
login_module = require('./../login.js'); login_module = require('./../login.js');
passport.use(new passportLocal(function(user, password, next) { passport.use(new passportLocal(function(user, password, next) {
@@ -16,10 +18,10 @@
}); });
})); }));
if (global.config['social:twitter:key'] && global.config['social:twitter:secret']) { if (meta.config['social:twitter:key'] && meta.config['social:twitter:secret']) {
passport.use(new passportTwitter({ passport.use(new passportTwitter({
consumerKey: global.config['social:twitter:key'], consumerKey: meta.config['social:twitter:key'],
consumerSecret: global.config['social:twitter:secret'], consumerSecret: meta.config['social:twitter:secret'],
callbackURL: nconf.get('url') + 'auth/twitter/callback' callbackURL: nconf.get('url') + 'auth/twitter/callback'
}, function(token, tokenSecret, profile, done) { }, function(token, tokenSecret, profile, done) {
login_module.loginViaTwitter(profile.id, profile.username, profile.photos, function(err, user) { login_module.loginViaTwitter(profile.id, profile.username, profile.photos, function(err, user) {
@@ -31,10 +33,10 @@
login_strategies.push('twitter'); login_strategies.push('twitter');
} }
if (global.config['social:google:id'] && global.config['social:google:secret']) { if (meta.config['social:google:id'] && meta.config['social:google:secret']) {
passport.use(new passportGoogle({ passport.use(new passportGoogle({
clientID: global.config['social:google:id'], clientID: meta.config['social:google:id'],
clientSecret: global.config['social:google:secret'], clientSecret: meta.config['social:google:secret'],
callbackURL: nconf.get('url') + 'auth/google/callback' callbackURL: nconf.get('url') + 'auth/google/callback'
}, function(accessToken, refreshToken, profile, done) { }, function(accessToken, refreshToken, profile, done) {
login_module.loginViaGoogle(profile.id, profile.displayName, profile.emails[0].value, function(err, user) { login_module.loginViaGoogle(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
@@ -46,10 +48,10 @@
login_strategies.push('google'); login_strategies.push('google');
} }
if (global.config['social:facebook:app_id'] && global.config['social:facebook:secret']) { if (meta.config['social:facebook:app_id'] && meta.config['social:facebook:secret']) {
passport.use(new passportFacebook({ passport.use(new passportFacebook({
clientID: global.config['social:facebook:app_id'], clientID: meta.config['social:facebook:app_id'],
clientSecret: global.config['social:facebook:secret'], clientSecret: meta.config['social:facebook:secret'],
callbackURL: nconf.get('url') + 'auth/facebook/callback' callbackURL: nconf.get('url') + 'auth/facebook/callback'
}, function(accessToken, refreshToken, profile, done) { }, function(accessToken, refreshToken, profile, done) {
login_module.loginViaFacebook(profile.id, profile.displayName, profile.emails[0].value, function(err, user) { login_module.loginViaFacebook(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
@@ -75,7 +77,7 @@
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
} }
Auth.get_login_strategies = function() { Auth.get_login_strategies = function() {
return login_strategies; return login_strategies;
@@ -85,7 +87,7 @@
app.get('/logout', function(req, res) { app.get('/logout', function(req, res) {
if (req.user && req.user.uid > 0) { if (req.user && req.user.uid > 0) {
console.log('info: [Auth] Session ' + req.sessionID + ' logout (uid: ' + req.user.uid + ')'); winston.info('[Auth] Session ' + req.sessionID + ' logout (uid: ' + req.user.uid + ')');
login_module.logout(req.sessionID, function(logout) { login_module.logout(req.sessionID, function(logout) {
req.logout(); req.logout();
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
@@ -126,33 +128,43 @@
app.get('/reset/:code', function(req, res) { app.get('/reset/:code', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + templates['reset_code'].parse({ reset_code: req.params.code }) + templates['footer']); res.send(header + app.create_route('reset/'+req.params.code) + templates['footer']);
}); });
}); });
app.get('/reset', function(req, res) { app.get('/reset', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
console.log(header); res.send(header + app.create_route('reset') + templates['footer']);
res.send(header + templates['reset'] + templates['footer']);
}); });
}); });
app.post('/login', function(req, res, next) {
app.post('/login', passport.authenticate('local'), function(req, res) { passport.authenticate('local', function(err, user, info) {
res.json({success:1}); if(err) {
return next(err);
}
if (!user) {
return res.send({ success : false, message : info.message });
}
req.login({
uid: user.uid
}, function() {
res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
}); });
app.post('/register', function(req, res) {
users.create(req.body.username, req.body.password, req.body.email, function(err, uid) {
if (err === null && uid > 0) { app.post('/register', function(req, res) {
user.create(req.body.username, req.body.password, req.body.email, function(err, uid) {
if (err === null && uid) {
req.login({ req.login({
uid: uid uid: uid
}, function() { }, function() {
res.redirect(global.nconf.get('relative_path') + '/'); res.redirect(nconf.get('relative_path') + '/');
}); });
} else { } else {
res.redirect(global.nconf.get('relative_path') + '/register'); res.redirect(nconf.get('relative_path') + '/register');
} }
}); });
}); });

View File

@@ -4,16 +4,17 @@ var user = require('./../user.js'),
fs = require('fs'), fs = require('fs'),
utils = require('./../../public/src/utils.js'), utils = require('./../../public/src/utils.js'),
path = require('path'), path = require('path'),
marked = require('marked'); marked = require('marked'),
winston = require('winston');
(function(User) { (function(User) {
User.create_routes = function(app) { User.create_routes = function(app) {
app.get('/uid/:uid', function(req, res) { app.get('/uid/:uid', function(req, res) {
if(!req.params.uid) if(!req.params.uid)
return res.redirect('/404'); return res.redirect('/404');
user.getUserData(req.params.uid, function(data) { user.getUserData(req.params.uid, function(data) {
if(data) { if(data) {
res.send(data); res.send(data);
@@ -21,7 +22,7 @@ var user = require('./../user.js'),
res.json(404, {error:"User doesn't exist!"}); res.json(404, {error:"User doesn't exist!"});
} }
}); });
}); });
app.get('/users', function(req, res) { app.get('/users', function(req, res) {
@@ -29,25 +30,25 @@ var user = require('./../user.js'),
res.send(header + app.create_route("users", "users") + templates['footer']); res.send(header + app.create_route("users", "users") + templates['footer']);
}); });
}); });
app.get('/users-latest', function(req, res) { app.get('/users-latest', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-latest", "users") + templates['footer']); res.send(header + app.create_route("users-latest", "users") + templates['footer']);
}); });
}); });
app.get('/users-sort-posts', function(req, res) { app.get('/users-sort-posts', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-sort-posts", "users") + templates['footer']); res.send(header + app.create_route("users-sort-posts", "users") + templates['footer']);
}); });
}); });
app.get('/users-sort-reputation', function(req, res) { app.get('/users-sort-reputation', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-sort-reputation", "users") + templates['footer']); res.send(header + app.create_route("users-sort-reputation", "users") + templates['footer']);
}); });
}); });
app.get('/users-search', function(req, res) { app.get('/users-search', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-search", "users") + templates['footer']); res.send(header + app.create_route("users-search", "users") + templates['footer']);
@@ -63,23 +64,22 @@ var user = require('./../user.js'),
user.get_uid_by_userslug(req.params.userslug, function(uid) { user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) { if(!uid) {
next(); return next();
return;
} }
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug, 'account') + templates['footer']); res.send(header + app.create_route('users/' + req.params.userslug, 'account') + templates['footer']);
}); });
});
});
}); });
app.get('/users/:userslug/edit', function(req, res) { app.get('/users/:userslug/edit', function(req, res) {
if(!req.user) if(!req.user)
return res.redirect('/403'); return res.redirect('/403');
user.getUserField(req.user.uid, 'userslug', function(userslug) { user.getUserField(req.user.uid, 'userslug', function(err, userslug) {
if(req.params.userslug && userslug === req.params.userslug) { if(req.params.userslug && userslug === req.params.userslug) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/edit','accountedit') + templates['footer']); res.send(header + app.create_route('users/'+req.params.userslug+'/edit','accountedit') + templates['footer']);
@@ -87,46 +87,46 @@ var user = require('./../user.js'),
} else { } else {
return res.redirect('/404'); return res.redirect('/404');
} }
}); });
}); });
app.get('/users/:userslug/settings', function(req, res) { app.get('/users/:userslug/settings', function(req, res) {
if(!req.user) if(!req.user)
return res.redirect('/403'); return res.redirect('/403');
user.getUserField(req.user.uid, 'userslug', function(userslug) { user.getUserField(req.user.uid, 'userslug', function(err, userslug) {
if(req.params.userslug && userslug === req.params.userslug) { if(req.params.userslug && userslug === req.params.userslug) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/settings','accountsettings') + templates['footer']); res.send(header + app.create_route('users/'+req.params.userslug+'/settings','accountsettings') + templates['footer']);
}) })
} else { } else {
return res.redirect('/404'); return res.redirect('/404');
} }
}); });
}); });
app.post('/users/uploadpicture', function(req, res) { app.post('/users/uploadpicture', function(req, res) {
if(!req.user) if(!req.user)
return res.redirect('/403'); return res.redirect('/403');
if(req.files.userPhoto.size > 262144) { if(req.files.userPhoto.size > 262144) {
res.send({ res.send({
error: 'Images must be smaller than 256kb!' error: 'Images must be smaller than 256kb!'
}); });
return; return;
} }
var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
if(allowedTypes.indexOf(req.files.userPhoto.type) === -1) { if(allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
res.send({ res.send({
error: 'Allowed image types are png, jpg and gif!' error: 'Allowed image types are png, jpg and gif!'
}); });
return; return;
} }
user.getUserField(req.user.uid, 'uploadedpicture', function(oldpicture) { user.getUserField(req.user.uid, 'uploadedpicture', function(err, oldpicture) {
if(!oldpicture) { if(!oldpicture) {
uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res); uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res);
return; return;
@@ -135,15 +135,15 @@ var user = require('./../user.js'),
var absolutePath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), path.basename(oldpicture)); var absolutePath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), path.basename(oldpicture));
fs.unlink(absolutePath, function(err) { fs.unlink(absolutePath, function(err) {
if(err) { if(err) {
console.error('[%d] %s', Date.now(), + err); winston.error('[%d] %s', Date.now(), + err);
} }
uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res); uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res);
}); });
}); });
}); });
function uploadUserPicture(uid, extension, tempPath, res) { function uploadUserPicture(uid, extension, tempPath, res) {
if(!extension) { if(!extension) {
res.send({ res.send({
@@ -154,10 +154,9 @@ var user = require('./../user.js'),
var filename = uid + '-profileimg' + extension; var filename = uid + '-profileimg' + extension;
var uploadPath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), filename); var uploadPath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), filename);
// @todo move to proper logging code - this should only be temporary winston.info('Attempting upload to: '+ uploadPath);
console.log('Info: Attempting upload to: '+ uploadPath);
var is = fs.createReadStream(tempPath); var is = fs.createReadStream(tempPath);
var os = fs.createWriteStream(uploadPath); var os = fs.createWriteStream(uploadPath);
@@ -176,11 +175,7 @@ var user = require('./../user.js'),
height: 128 height: 128
}, function(err, stdout, stderr){ }, function(err, stdout, stderr){
if (err) { if (err) {
// @todo: better logging method; for now, send to stderr. winston.err(err.message, err.stack);
// ideally, this should be happening in another process
// to avoid poisoning the main process on error or allowing a significant problem
// to crash the main process
console.error('[%d] %s', Date.now(), + err);
} }
res.json({ path: imageUrl }); res.json({ path: imageUrl });
@@ -189,7 +184,7 @@ var user = require('./../user.js'),
}); });
os.on('error', function(err) { os.on('error', function(err) {
console.error('[%d] %s', Date.now(), + err); winston.error('[%d] %s', Date.now(), + err);
}); });
is.pipe(os); is.pipe(os);
@@ -199,24 +194,24 @@ var user = require('./../user.js'),
if(!req.user) if(!req.user)
return res.redirect('/403'); return res.redirect('/403');
user.get_uid_by_userslug(req.params.userslug, function(uid) { user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) { if(!uid) {
res.redirect('/404'); res.redirect('/404');
return; return;
} }
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/following','following') + templates['footer']); res.send(header + app.create_route('users/'+req.params.userslug+'/following','following') + templates['footer']);
}); });
}); });
}); });
app.get('/users/:userslug/followers', function(req, res) { app.get('/users/:userslug/followers', function(req, res) {
if(!req.user) if(!req.user)
return res.redirect('/403'); return res.redirect('/403');
user.get_uid_by_userslug(req.params.userslug, function(uid) { user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) { if(!uid) {
res.redirect('/404'); res.redirect('/404');
@@ -228,6 +223,22 @@ var user = require('./../user.js'),
}); });
}); });
app.get('/users/:userslug/favourites', function(req, res) {
if(!req.user)
return res.redirect('/403');
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
res.redirect('/404');
return;
}
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/favourites','favourites') + templates['footer']);
});
});
});
app.get('/api/users/:userslug/following', function(req, res) { app.get('/api/users/:userslug/following', function(req, res) {
var callerUID = req.user ? req.user.uid : 0; var callerUID = req.user ? req.user.uid : 0;
@@ -238,7 +249,7 @@ var user = require('./../user.js'),
userData.followingCount = followingData.length; userData.followingCount = followingData.length;
res.json(userData); res.json(userData);
}); });
} else { } else {
res.json(404, { error: 'User not found!' }) ; res.json(404, { error: 'User not found!' }) ;
} }
@@ -247,7 +258,7 @@ var user = require('./../user.js'),
app.get('/api/users/:userslug/followers', function(req, res) { app.get('/api/users/:userslug/followers', function(req, res) {
var callerUID = req.user ? req.user.uid : 0; var callerUID = req.user ? req.user.uid : 0;
getUserDataByUserSlug(req.params.userslug, callerUID, function(userData) { getUserDataByUserSlug(req.params.userslug, callerUID, function(userData) {
if(userData) { if(userData) {
user.getFollowers(userData.uid, function(followersData) { user.getFollowers(userData.uid, function(followersData) {
@@ -257,7 +268,7 @@ var user = require('./../user.js'),
}); });
} else { } else {
res.json(404, { error: 'User not found!' }) ; res.json(404, { error: 'User not found!' }) ;
} }
}); });
}); });
@@ -269,7 +280,7 @@ var user = require('./../user.js'),
}); });
}); });
app.get('/api/users/:userslug/settings', function(req, res) { app.get('/api/users/:userslug/settings', function(req, res, next) {
var callerUID = req.user ? req.user.uid : 0; var callerUID = req.user ? req.user.uid : 0;
user.get_uid_by_userslug(req.params.userslug, function(uid) { user.get_uid_by_userslug(req.params.userslug, function(uid) {
@@ -277,12 +288,15 @@ var user = require('./../user.js'),
res.json(404, { error: 'User not found!' }) ; res.json(404, { error: 'User not found!' }) ;
return; return;
} }
if(uid !== callerUID || callerUID === "0") { if(uid !== callerUID || callerUID === "0") {
res.json(403, { error: 'Not allowed!' }); res.json(403, { error: 'Not allowed!' });
return; return;
} }
user.getUserFields(uid, ['username','userslug','showemail'], function(userData) { user.getUserFields(uid, ['username','userslug','showemail'], function(err, userData) {
if(err)
return next(err);
if(userData) { if(userData) {
if(userData.showemail && userData.showemail === "1") if(userData.showemail && userData.showemail === "1")
userData.showemail = "checked"; userData.showemail = "checked";
@@ -291,9 +305,42 @@ var user = require('./../user.js'),
res.json(userData); res.json(userData);
} else { } else {
res.json(404, { error: 'User not found!' }) ; res.json(404, { error: 'User not found!' }) ;
} }
}); });
}); });
});
app.get('/api/users/:userslug/favourites', function(req, res, next) {
var callerUID = req.user ? req.user.uid : 0;
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
res.json(404, { error: 'User not found!' }) ;
return;
}
if(uid !== callerUID || callerUID === "0") {
res.json(403, { error: 'Not allowed!' });
return;
}
user.getUserFields(uid, ['username','userslug'], function(err, userData) {
if(err)
return next(err);
if(userData) {
posts.getFavourites(uid, function(err, posts) {
if(err)
return next(err);
userData.posts = posts;
userData.show_nofavourites = posts.length?'hide':'show';
res.json(userData);
});
} else {
res.json(404, { error: 'User not found!' }) ;
}
});
});
}); });
app.get('/api/users/:userslug', function(req, res) { app.get('/api/users/:userslug', function(req, res) {
@@ -307,13 +354,17 @@ var user = require('./../user.js'),
userData.posts = posts.filter(function(p) {return p.deleted !== "1";}); userData.posts = posts.filter(function(p) {return p.deleted !== "1";});
userData.isFollowing = isFollowing; userData.isFollowing = isFollowing;
userData.signature = postTools.markdownToHTML(userData.signature, true); userData.signature = postTools.markdownToHTML(userData.signature, true);
if(!userData.profileviews)
userData.profileviews = 1;
if(callerUID !== userData.uid)
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
res.json(userData); res.json(userData);
}); });
}); });
} else { } else {
res.json(404, { error: 'User not found!' }) ; res.json(404, { error: 'User not found!' }) ;
} }
}); });
}); });
app.get('/api/users', getUsersSortedByJoinDate); app.get('/api/users', getUsersSortedByJoinDate);
@@ -321,17 +372,17 @@ var user = require('./../user.js'),
app.get('/api/users-sort-reputation', getUsersSortedByReputation); app.get('/api/users-sort-reputation', getUsersSortedByReputation);
app.get('/api/users-latest', getUsersSortedByJoinDate); app.get('/api/users-latest', getUsersSortedByJoinDate);
app.get('/api/users-search', getUsersForSearch); app.get('/api/users-search', getUsersForSearch);
function getUsersSortedByJoinDate(req, res) { function getUsersSortedByJoinDate(req, res) {
user.getUsers('users:joindate', 0, 49, function(err, data) { user.getUsers('users:joindate', 0, 49, function(err, data) {
res.json({ search_display: 'none',loadmore_display:'block', users:data }); res.json({ search_display: 'none', loadmore_display:'block', users:data });
}); });
} }
function getUsersSortedByPosts(req, res) { function getUsersSortedByPosts(req, res) {
user.getUsers('users:postcount', 0, 49, function(err, data) { user.getUsers('users:postcount', 0, 49, function(err, data) {
res.json({ search_display: 'none',loadmore_display:'block', users:data }); res.json({ search_display: 'none', loadmore_display:'block', users:data });
}); });
} }
@@ -340,19 +391,19 @@ var user = require('./../user.js'),
res.json({ search_display: 'none', loadmore_display:'block', users:data }); res.json({ search_display: 'none', loadmore_display:'block', users:data });
}); });
} }
function getUsersForSearch(req, res) { function getUsersForSearch(req, res) {
res.json({ search_display: 'block', loadmore_display:'none', users: [] }); res.json({ search_display: 'block', loadmore_display:'none', users: [] });
} }
function getUserDataByUserSlug(userslug, callerUID, callback) { function getUserDataByUserSlug(userslug, callerUID, callback) {
user.get_uid_by_userslug(userslug, function(uid) { user.get_uid_by_userslug(userslug, function(uid) {
if(uid === null) { if(uid === null) {
callback(null); callback(null);
return; return;
} }
user.getUserData(uid, function(data) { user.getUserData(uid, function(data) {
if(data) { if(data) {
data.joindate = utils.relativeTime(data.joindate); data.joindate = utils.relativeTime(data.joindate);
@@ -362,19 +413,20 @@ var user = require('./../user.js'),
} else { } else {
data.age = new Date().getFullYear() - new Date(data.birthday).getFullYear(); data.age = new Date().getFullYear() - new Date(data.birthday).getFullYear();
} }
function canSeeEmail() { function canSeeEmail() {
return callerUID === uid || (data.email && (data.showemail && data.showemail === "1")); return callerUID === uid || (data.email && (data.showemail && data.showemail === "1"));
} }
if(!canSeeEmail()) if(!canSeeEmail())
data.email = ""; data.email = "";
if(callerUID === uid && (!data.showemail || data.showemail === "0")) if(callerUID === uid && (!data.showemail || data.showemail === "0"))
data.emailClass = ""; data.emailClass = "";
else else
data.emailClass = "hide"; data.emailClass = "hide";
data.show_banned = data.banned === '1'?'':'hide';
data.uid = uid; data.uid = uid;
data.yourid = callerUID; data.yourid = callerUID;
@@ -391,7 +443,7 @@ var user = require('./../user.js'),
callback(null); callback(null);
} }
}); });
}); });
} }

View File

@@ -56,7 +56,7 @@ var path = require('path'),
async.parallel([sitemap.getStaticUrls, sitemap.getDynamicUrls], function(err, urls) { async.parallel([sitemap.getStaticUrls, sitemap.getDynamicUrls], function(err, urls) {
var urls = urls[0].concat(urls[1]), var urls = urls[0].concat(urls[1]),
map = sm.createSitemap({ map = sm.createSitemap({
hostname: global.nconf.get('url'), hostname: nconf.get('url'),
cacheTime: 600000, cacheTime: 600000,
urls: urls urls: urls
}), }),

View File

@@ -6,7 +6,9 @@ var RDB = require('./redis.js'),
notifications = require('./notifications.js'), notifications = require('./notifications.js'),
posts = require('./posts'), posts = require('./posts'),
reds = require('reds'), reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch'); topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
meta = require('./meta');
(function(ThreadTools) { (function(ThreadTools) {
@@ -16,10 +18,10 @@ var RDB = require('./redis.js'),
callback(!!ismember || false); callback(!!ismember || false);
}); });
} }
ThreadTools.privileges = function(tid, uid, callback) { ThreadTools.privileges = function(tid, uid, callback) {
//todo: break early if one condition is true //todo: break early if one condition is true
function getCategoryPrivileges(next) { function getCategoryPrivileges(next) {
topics.getTopicField(tid, 'cid', function(err, cid) { topics.getTopicField(tid, 'cid', function(err, cid) {
categories.privileges(cid, uid, function(privileges) { categories.privileges(cid, uid, function(privileges) {
@@ -29,11 +31,12 @@ var RDB = require('./redis.js'),
} }
function hasEnoughRep(next) { function hasEnoughRep(next) {
user.getUserField(uid, 'reputation', function(reputation) { user.getUserField(uid, 'reputation', function(err, reputation) {
next(null, reputation >= global.config['privileges:manage_topic']); if (err) return next(null, false);
next(null, reputation >= meta.config['privileges:manage_topic']);
}); });
} }
async.parallel([getCategoryPrivileges, hasEnoughRep], function(err, results) { async.parallel([getCategoryPrivileges, hasEnoughRep], function(err, results) {
callback({ callback({
@@ -86,8 +89,8 @@ var RDB = require('./redis.js'),
ThreadTools.delete = function(tid, uid, callback) { ThreadTools.delete = function(tid, uid, callback) {
ThreadTools.privileges(tid, uid, function(privileges) { ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable || uid === -1) { if (privileges.editable || uid === -1) {
topics.setTopicField(tid, 'deleted', 1); topics.delete(tid);
ThreadTools.lock(tid, uid); ThreadTools.lock(tid, uid);
topicSearch.remove(tid); topicSearch.remove(tid);
@@ -106,15 +109,15 @@ var RDB = require('./redis.js'),
ThreadTools.privileges(tid, uid, function(privileges) { ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) { if (privileges.editable) {
topics.setTopicField(tid, 'deleted', 0); topics.restore(tid);
ThreadTools.unlock(tid, uid); ThreadTools.unlock(tid, uid);
if (socket) { io.sockets.in('topic_' + tid).emit('event:topic_restored', {
io.sockets.in('topic_' + tid).emit('event:topic_restored', { tid: tid,
tid: tid, status: 'ok'
status: 'ok' });
});
if (socket) {
socket.emit('api:topic.restore', { socket.emit('api:topic.restore', {
status: 'ok', status: 'ok',
tid: tid tid: tid
@@ -131,12 +134,12 @@ var RDB = require('./redis.js'),
ThreadTools.pin = function(tid, uid, socket) { ThreadTools.pin = function(tid, uid, socket) {
ThreadTools.privileges(tid, uid, function(privileges) { ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) { if (privileges.editable) {
topics.setTopicField(tid, 'pinned', 1); topics.setTopicField(tid, 'pinned', 1);
topics.getTopicField(tid, 'cid', function(err, cid) { topics.getTopicField(tid, 'cid', function(err, cid) {
RDB.zadd('categories:' + cid + ':tid', Math.pow(2,53), tid); RDB.zadd('categories:' + cid + ':tid', Math.pow(2,53), tid);
}); });
if (socket) { if (socket) {
io.sockets.in('topic_' + tid).emit('event:topic_pinned', { io.sockets.in('topic_' + tid).emit('event:topic_pinned', {
tid: tid, tid: tid,
@@ -155,9 +158,9 @@ var RDB = require('./redis.js'),
ThreadTools.unpin = function(tid, uid, socket) { ThreadTools.unpin = function(tid, uid, socket) {
ThreadTools.privileges(tid, uid, function(privileges) { ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) { if (privileges.editable) {
topics.setTopicField(tid, 'pinned', 0); topics.setTopicField(tid, 'pinned', 0);
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(topicData) { topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
RDB.zadd('categories:' + topicData.cid + ':tid', topicData.lastposttime, tid); RDB.zadd('categories:' + topicData.cid + ':tid', topicData.lastposttime, tid);
}); });
if (socket) { if (socket) {
@@ -176,23 +179,23 @@ var RDB = require('./redis.js'),
} }
ThreadTools.move = function(tid, cid, socket) { ThreadTools.move = function(tid, cid, socket) {
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(topicData) { topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
var oldCid = topicData.cid; var oldCid = topicData.cid;
var multi = RDB.multi(); var multi = RDB.multi();
multi.zrem('categories:' + oldCid + ':tid', tid); multi.zrem('categories:' + oldCid + ':tid', tid);
multi.zadd('categories:' + cid + ':tid', topicData.lastposttime, tid); multi.zadd('categories:' + cid + ':tid', topicData.lastposttime, tid);
multi.exec(function(err, result) { multi.exec(function(err, result) {
if (!err && result === 1) { if (!err && result[0] === 1 && result[1] === 1) {
topics.setTopicField(tid, 'cid', cid); topics.setTopicField(tid, 'cid', cid);
categories.moveRecentReplies(tid, oldCid, cid, function(err, data) { categories.moveRecentReplies(tid, oldCid, cid, function(err, data) {
if(err) { if(err) {
console.log(err); winston.err(err);
} }
}); });
@@ -257,7 +260,7 @@ var RDB = require('./redis.js'),
ThreadTools.notify_followers = function(tid, exceptUid) { ThreadTools.notify_followers = function(tid, exceptUid) {
async.parallel([ async.parallel([
function(next) { function(next) {
topics.getTopicField(tid, 'title', function(err, title) { topics.getTopicField(tid, 'title', function(err, title) {
topics.getTeaser(tid, function(err, teaser) { topics.getTeaser(tid, function(err, teaser) {
if (!err) { if (!err) {
@@ -267,8 +270,8 @@ var RDB = require('./redis.js'),
} else next(err); } else next(err);
}); });
}); });
}, },
function(next) { function(next) {
ThreadTools.get_followers(tid, function(err, followers) { ThreadTools.get_followers(tid, function(err, followers) {
@@ -289,16 +292,15 @@ var RDB = require('./redis.js'),
var numPosts = posts.length; var numPosts = posts.length;
if(!numPosts) if(!numPosts)
return callback(new Error('no-undeleted-pids-found')); return callback(new Error('no-undeleted-pids-found'));
while(numPosts--) { while(numPosts--) {
if(posts[numPosts].deleted !== '1') { if(posts[numPosts].deleted !== '1') {
callback(null, posts[numPosts].pid); callback(null, posts[numPosts].pid);
return; return;
} }
} }
// If we got here, nothing was found...
callback(new Error('no-undeleted-pids-found')); callback(new Error('no-undeleted-pids-found'));
}); });
} }
}(exports)); }(exports));

View File

@@ -20,8 +20,6 @@ marked.setOptions({
(function(Topics) { (function(Topics) {
Topics.getTopicData = function(tid, callback) { Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) { RDB.hgetall('topic:' + tid, function(err, data) {
if(err === null) if(err === null)
@@ -33,8 +31,7 @@ marked.setOptions({
Topics.getTopicDataWithUsername = function(tid, callback) { Topics.getTopicDataWithUsername = function(tid, callback) {
Topics.getTopicData(tid, function(topic) { Topics.getTopicData(tid, function(topic) {
user.getUserField(topic.uid, 'username', function(username) { user.getUserField(topic.uid, 'username', function(err, username) {
topic.username = username; topic.username = username;
callback(topic); callback(topic);
}); });
@@ -45,15 +42,15 @@ marked.setOptions({
posts.getPostsByTid(tid, start, end, function(postData) { posts.getPostsByTid(tid, start, end, function(postData) {
if(Array.isArray(postData) && !postData.length) if(Array.isArray(postData) && !postData.length)
return callback([]); return callback([]);
function getFavouritesData(next) { function getFavouritesData(next) {
var pids = []; var pids = [];
for(var i=0; i<postData.length; ++i) for(var i=0; i<postData.length; ++i)
pids.push(postData[i].pid); pids.push(postData[i].pid);
favourites.getFavouritesByPostIDs(pids, current_user, function(fav_data) { favourites.getFavouritesByPostIDs(pids, current_user, function(fav_data) {
next(null, fav_data); next(null, fav_data);
}); });
} }
function addUserInfoToPosts(next) { function addUserInfoToPosts(next) {
@@ -63,26 +60,28 @@ marked.setOptions({
}); });
} }
async.each(postData, iterator, function(err) { async.each(postData, iterator, function(err) {
next(err, null); next(err, null);
}); });
} }
function getPrivileges(next) { function getPrivileges(next) {
threadTools.privileges(tid, current_user, function(privData) { threadTools.privileges(tid, current_user, function(privData) {
next(null, privData); next(null, privData);
}); });
} }
async.parallel([getFavouritesData, addUserInfoToPosts, getPrivileges], function(err, results) { async.parallel([getFavouritesData, addUserInfoToPosts, getPrivileges], function(err, results) {
var fav_data = results[0], var fav_data = results[0],
privileges = results[2]; privileges = results[2];
for(var i=0; i<postData.length; ++i) { for(var i=0; i<postData.length; ++i) {
postData[i].fav_button_class = fav_data[postData[i].pid]? 'btn-warning' : '';
postData[i].fav_star_class = fav_data[postData[i].pid] ? 'icon-star' : 'icon-star-empty'; postData[i].fav_star_class = fav_data[postData[i].pid] ? 'icon-star' : 'icon-star-empty';
postData[i]['display_moderator_tools'] = (postData[i].uid == current_user || privileges.editable) ? 'show' : 'none'; postData[i]['display_moderator_tools'] = (postData[i].uid == current_user || privileges.editable) ? 'show' : 'none';
postData[i].show_banned = postData[i].user_banned === '1'?'show':'hide';
} }
callback(postData); callback(postData);
}); });
}); });
@@ -99,9 +98,9 @@ marked.setOptions({
var timestamp = Date.now(); var timestamp = Date.now();
var args = [ 'topics:recent', '+inf', timestamp - 86400000, 'WITHSCORES', 'LIMIT', start, end - start + 1]; var args = [ 'topics:recent', '+inf', timestamp - 86400000, 'WITHSCORES', 'LIMIT', start, end - start + 1];
RDB.zrevrangebyscore(args, function(err, tids) { RDB.zrevrangebyscore(args, function(err, tids) {
var latestTopics = { var latestTopics = {
'category_name' : 'Recent', 'category_name' : 'Recent',
'show_sidebar' : 'hidden', 'show_sidebar' : 'hidden',
@@ -124,7 +123,49 @@ marked.setOptions({
}); });
}); });
} }
Topics.getTotalUnread = function(uid, callback) {
var unreadTids = [],
start = 0,
stop = 21,
done = false;
async.whilst(
function () { return unreadTids.length < 21 && !done; },
function (callback) {
RDB.zrevrange('topics:recent', start, stop, function(err, tids) {
if(err)
return callback(err);
if(tids && !tids.length) {
done = true;
return callback(null);
}
Topics.hasReadTopics(tids, uid, function(read) {
var newtids = tids.filter(function(tid, index, self) {
return read[index] === 0;
});
unreadTids.push.apply(unreadTids, newtids);
start = stop + 1;
stop = start + 21;
callback(null);
});
});
},
function (err) {
callback({
count: unreadTids.length
});
}
);
};
Topics.getUnreadTopics = function(uid, start, stop, callback) { Topics.getUnreadTopics = function(uid, start, stop, callback) {
var unreadTopics = { var unreadTopics = {
@@ -137,65 +178,82 @@ marked.setOptions({
'topics' : [] 'topics' : []
}; };
RDB.zrevrange('topics:recent', start, stop, function (err, tids) { function noUnreadTopics() {
unreadTopics.no_topics_message = 'show';
function noUnreadTopics() { unreadTopics.show_markallread_button = 'hidden';
unreadTopics.no_topics_message = 'show'; callback(unreadTopics);
unreadTopics.show_markallread_button = 'hidden'; }
function sendUnreadTopics(topicIds) {
Topics.getTopicsByTids(topicIds, uid, function(topicData) {
unreadTopics.topics = topicData;
unreadTopics.nextStart = start + topicIds.length;
if(!topicData || topicData.length === 0)
unreadTopics.no_topics_message = 'show';
if(uid === 0 || topicData.length === 0)
unreadTopics.show_markallread_button = 'hidden';
callback(unreadTopics); callback(unreadTopics);
} });
}
function sendUnreadTopics(topicIds) {
Topics.getTopicsByTids(topicIds, uid, function(topicData) { var unreadTids = [],
unreadTopics.topics = topicData; done = false;
unreadTopics.nextStart = start + tids.length;
if(uid === 0) async.whilst(
unreadTopics.show_markallread_button = 'hidden'; function () { return unreadTids.length < 20 && !done; },
callback(unreadTopics); function (callback) {
}); RDB.zrevrange('topics:recent', start, stop, function(err, tids) {
} if(err)
return callback(err);
if (!tids || !tids.length) {
noUnreadTopics(); if(tids && !tids.length) {
return; done = true;
} return callback(null);
if(uid === 0) {
sendUnreadTopics(tids);
} else {
Topics.hasReadTopics(tids, uid, function(read) {
var unreadTids = tids.filter(function(tid, index, self) {
return read[index] === 0;
});
if (!unreadTids || !unreadTids.length) {
noUnreadTopics();
return;
} }
if(uid === 0) {
unreadTids.push.apply(unreadTids, tids);
callback(null);
} else {
Topics.hasReadTopics(tids, uid, function(read) {
var newtids = tids.filter(function(tid, index, self) {
return read[index] === 0;
});
unreadTids.push.apply(unreadTids, newtids);
start = stop + 1;
stop = start + 19;
callback(null);
});
}
});
},
function (err) {
if(err)
return callback([]);
if(unreadTids.length)
sendUnreadTopics(unreadTids); sendUnreadTopics(unreadTids);
}); else
noUnreadTopics();
} }
}); );
} }
Topics.getTopicsByTids = function(tids, current_user, callback, category_id) { Topics.getTopicsByTids = function(tids, current_user, callback, category_id) {
var retrieved_topics = []; var retrieved_topics = [];
if(!Array.isArray(tids) || tids.length === 0) { if(!Array.isArray(tids) || tids.length === 0) {
callback(retrieved_topics); callback(retrieved_topics);
return; return;
} }
function getTopicInfo(topicData, callback) { function getTopicInfo(topicData, callback) {
function getUserName(next) { function getUserInfo(next) {
user.getUserField(topicData.uid, 'username', function(username) { user.getUserFields(topicData.uid, ['username'], next);
next(null, username);
});
} }
function hasReadTopic(next) { function hasReadTopic(next) {
@@ -217,17 +275,13 @@ marked.setOptions({
}); });
} }
async.parallel([getUserName, hasReadTopic, getTeaserInfo, getPrivileges], function(err, results) { async.parallel([getUserInfo, hasReadTopic, getTeaserInfo, getPrivileges], function(err, results) {
var username = results[0],
hasReadTopic = results[1],
teaserInfo = results[2],
privileges = results[3];
callback({ callback({
username: username, username: results[0].username,
hasread: hasReadTopic, userbanned: results[0].banned,
teaserInfo: teaserInfo, hasread: results[1],
privileges: privileges teaserInfo: results[2],
privileges: results[3]
}); });
}); });
} }
@@ -242,7 +296,7 @@ marked.setOptions({
if(!topicData) { if(!topicData) {
return callback(null); return callback(null);
} }
getTopicInfo(topicData, function(topicInfo) { getTopicInfo(topicData, function(topicInfo) {
topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none'; topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none';
@@ -260,12 +314,12 @@ marked.setOptions({
if (isTopicVisible(topicData, topicInfo)) if (isTopicVisible(topicData, topicInfo))
retrieved_topics.push(topicData); retrieved_topics.push(topicData);
callback(null); callback(null);
}); });
}); });
} }
async.eachSeries(tids, loadTopic, function(err) { async.eachSeries(tids, loadTopic, function(err) {
if(!err) { if(!err) {
callback(retrieved_topics); callback(retrieved_topics);
@@ -276,7 +330,7 @@ marked.setOptions({
Topics.getTopicWithPosts = function(tid, current_user, callback) { Topics.getTopicWithPosts = function(tid, current_user, callback) {
threadTools.exists(tid, function(exists) { threadTools.exists(tid, function(exists) {
if (!exists) if (!exists)
return callback(new Error('Topic tid \'' + tid + '\' not found')); return callback(new Error('Topic tid \'' + tid + '\' not found'));
Topics.markAsRead(tid, current_user); Topics.markAsRead(tid, current_user);
@@ -297,8 +351,8 @@ marked.setOptions({
threadTools.privileges(tid, current_user, function(privData) { threadTools.privileges(tid, current_user, function(privData) {
next(null, privData); next(null, privData);
}); });
} }
function getCategoryData(next) { function getCategoryData(next) {
Topics.getCategoryData(tid, next); Topics.getCategoryData(tid, next);
} }
@@ -309,14 +363,14 @@ marked.setOptions({
callback(err, null); callback(err, null);
return; return;
} }
var topicData = results[0], var topicData = results[0],
topicPosts = results[1], topicPosts = results[1],
privileges = results[2], privileges = results[2],
categoryData = results[3]; categoryData = results[3];
var main_posts = topicPosts.splice(0, 1); var main_posts = topicPosts.splice(0, 1);
callback(null, { callback(null, {
'topic_name':topicData.title, 'topic_name':topicData.title,
'category_name':categoryData.name, 'category_name':categoryData.name,
@@ -364,7 +418,7 @@ marked.setOptions({
if (err) { if (err) {
throw new Error(err); throw new Error(err);
} }
var topicData = results[0], var topicData = results[0],
hasRead = results[1], hasRead = results[1],
teaser = results[2]; teaser = results[2];
@@ -375,7 +429,7 @@ marked.setOptions({
topicData.teaser_username = teaser.username || ''; topicData.teaser_username = teaser.username || '';
topicData.teaser_timestamp = teaser.timestamp ? utils.relativeTime(teaser.timestamp) : ''; topicData.teaser_timestamp = teaser.timestamp ? utils.relativeTime(teaser.timestamp) : '';
topicData.teaser_userpicture = teaser.picture; topicData.teaser_userpicture = teaser.picture;
callback(topicData); callback(topicData);
}); });
} }
@@ -417,7 +471,7 @@ marked.setOptions({
}); });
}); });
} }
Topics.markAllRead = function(uid, callback) { Topics.markAllRead = function(uid, callback) {
RDB.smembers('topics:tid', function(err, tids) { RDB.smembers('topics:tid', function(err, tids) {
if(err) { if(err) {
@@ -425,13 +479,13 @@ marked.setOptions({
callback(err, null); callback(err, null);
return; return;
} }
if(tids && tids.length) { if(tids && tids.length) {
for(var i=0; i<tids.length; ++i) { for(var i=0; i<tids.length; ++i) {
Topics.markAsRead(tids[i], uid); Topics.markAsRead(tids[i], uid);
} }
} }
callback(null, true); callback(null, true);
}); });
} }
@@ -445,15 +499,15 @@ marked.setOptions({
} }
Topics.markUnRead = function(tid) { Topics.markUnRead = function(tid) {
RDB.del('tid:' + tid + ':read_by_uid'); RDB.del('tid:' + tid + ':read_by_uid');
} }
Topics.markAsRead = function(tid, uid) { Topics.markAsRead = function(tid, uid) {
RDB.sadd(schema.topics(tid).read_by_uid, uid); RDB.sadd(schema.topics(tid).read_by_uid, uid);
Topics.getTopicField(tid, 'cid', function(err, cid) { Topics.getTopicField(tid, 'cid', function(err, cid) {
categories.isTopicsRead(cid, uid, function(read) { categories.isTopicsRead(cid, uid, function(read) {
if(read) { if(read) {
categories.markAsRead(cid, uid); categories.markAsRead(cid, uid);
@@ -466,9 +520,9 @@ marked.setOptions({
var batch = RDB.multi(); var batch = RDB.multi();
for (var i=0, ii=tids.length; i<ii; i++) { for (var i=0, ii=tids.length; i<ii; i++) {
batch.sismember(schema.topics(tids[i]).read_by_uid, uid); batch.sismember(schema.topics(tids[i]).read_by_uid, uid);
} }
batch.exec(function(err, hasRead) { batch.exec(function(err, hasRead) {
callback(hasRead); callback(hasRead);
}); });
@@ -483,9 +537,9 @@ marked.setOptions({
console.log(err); console.log(err);
callback(false); callback(false);
} }
}); });
} }
Topics.getTeasers = function(tids, callback) { Topics.getTeasers = function(tids, callback) {
var teasers = []; var teasers = [];
if (Array.isArray(tids)) { if (Array.isArray(tids)) {
@@ -506,7 +560,10 @@ marked.setOptions({
if (!err) { if (!err) {
posts.getPostFields(pid, ['content', 'uid', 'timestamp'], function(postData) { posts.getPostFields(pid, ['content', 'uid', 'timestamp'], function(postData) {
user.getUserFields(postData.uid, ['username', 'picture'], function(userData) { user.getUserFields(postData.uid, ['username', 'picture'], function(err, userData) {
if(err)
return callback(err, null);
var stripped = postData.content, var stripped = postData.content,
timestamp = postData.timestamp; timestamp = postData.timestamp;
@@ -532,34 +589,34 @@ marked.setOptions({
type: 'error', type: 'error',
timeout: 2000, timeout: 2000,
title: 'Title too short', title: 'Title too short',
message: "Please enter a longer title. At least " + config.minimumTitleLength + " characters.", message: "Please enter a longer title. At least " + meta.config.minimumTitleLength + " characters.",
alert_id: 'post_error' alert_id: 'post_error'
}); });
} }
Topics.post = function(uid, title, content, category_id, images, callback) { Topics.post = function(uid, title, content, category_id, images, callback) {
if (!category_id) if (!category_id)
throw new Error('Attempted to post without a category_id'); throw new Error('Attempted to post without a category_id');
if(content) if(content)
content = content.trim(); content = content.trim();
if(title) if(title)
title = title.trim(); title = title.trim();
if (uid === 0) { if (uid === 0) {
callback(new Error('not-logged-in'), null); callback(new Error('not-logged-in'), null);
return; return;
} else if(!title || title.length < config.minimumTitleLength) { } else if(!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null); callback(new Error('title-too-short'), null);
return; return;
} else if (!content || content.length < config.miminumPostLength) { } else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null); callback(new Error('content-too-short'), null);
return; return;
} }
user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(Date.now() - lastposttime < config.postDelay) { user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if(Date.now() - lastposttime < meta.config.postDelay) {
callback(new Error('too-many-posts'), null); callback(new Error('too-many-posts'), null);
return; return;
} }
@@ -590,9 +647,9 @@ marked.setOptions({
'postcount': 0, 'postcount': 0,
'locked': 0, 'locked': 0,
'deleted': 0, 'deleted': 0,
'pinned': 0 'pinned': 0
}); });
topicSearch.index(title, tid); topicSearch.index(title, tid);
RDB.set('topicslug:' + slug + ':tid', tid); RDB.set('topicslug:' + slug + ':tid', tid);
@@ -613,7 +670,6 @@ marked.setOptions({
posts.create(uid, tid, content, images, function(postData) { posts.create(uid, tid, content, images, function(postData) {
if (postData) { if (postData) {
RDB.lpush(schema.topics(tid).posts, postData.pid);
// Auto-subscribe the post creator to the newly created topic // Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid); threadTools.toggleFollow(tid, uid);
@@ -634,16 +690,9 @@ marked.setOptions({
Topics.getTopicField = function(tid, field, callback) { Topics.getTopicField = function(tid, field, callback) {
RDB.hget('topic:' + tid, field, callback); RDB.hget('topic:' + tid, field, callback);
} }
Topics.getTopicFields = function(tid, fields, callback) { Topics.getTopicFields = function(tid, fields, callback) {
RDB.hmgetObject('topic:' + tid, fields, function(err, data) { RDB.hmgetObject('topic:' + tid, fields, callback);
if(err === null) {
callback(data);
}
else {
console.log(err);
}
});
} }
Topics.setTopicField = function(tid, field, value) { Topics.setTopicField = function(tid, field, value) {
@@ -661,10 +710,10 @@ marked.setOptions({
} }
Topics.updateTimestamp = function(tid, timestamp) { Topics.updateTimestamp = function(tid, timestamp) {
RDB.zadd(schema.topics().recent, timestamp, tid); RDB.zadd('topics:recent', timestamp, tid);
Topics.setTopicField(tid, 'lastposttime', timestamp); Topics.setTopicField(tid, 'lastposttime', timestamp);
} }
Topics.addPostToTopic = function(tid, pid) { Topics.addPostToTopic = function(tid, pid) {
RDB.rpush('tid:' + tid + ':posts', pid); RDB.rpush('tid:' + tid + ':posts', pid);
} }
@@ -673,6 +722,18 @@ marked.setOptions({
RDB.lrange('tid:' + tid + ':posts', 0, -1, callback); RDB.lrange('tid:' + tid + ':posts', 0, -1, callback);
} }
Topics.delete = function(tid) {
Topics.setTopicField(tid, 'deleted', 1);
RDB.zrem('topics:recent', tid);
}
Topics.restore = function(tid) {
Topics.setTopicField(tid, 'deleted', 0);
Topics.getTopicField(tid, 'lastposttime', function(err, lastposttime) {
RDB.zadd('topics:recent', lastposttime, tid);
});
}
Topics.reIndexTopic = function(tid, callback) { Topics.reIndexTopic = function(tid, callback) {
Topics.getPids(tid, function(err, pids) { Topics.getPids(tid, function(err, pids) {
if(err) { if(err) {

View File

@@ -1,13 +1,14 @@
var RDB = require('./redis.js'), var RDB = require('./redis.js'),
async = require('async'); async = require('async'),
winston = require('winston'),
user = require('./user');
function upgradeCategory(cid, callback) { function upgradeCategory(cid, callback) {
RDB.type('categories:'+ cid +':tid', function(err, type) { RDB.type('categories:'+ cid +':tid', function(err, type) {
if (type === 'set') { if (type === 'set') {
RDB.smembers('categories:' + cid + ':tid', function(err, tids) { RDB.smembers('categories:' + cid + ':tid', function(err, tids) {
function moveTopic(tid, callback) { function moveTopic(tid, callback) {
RDB.hget('topic:' + tid, 'timestamp', function(err, timestamp) { RDB.hget('topic:' + tid, 'timestamp', function(err, timestamp) {
if(err) if(err)
@@ -17,81 +18,83 @@ function upgradeCategory(cid, callback) {
callback(null); callback(null);
}); });
} }
async.each(tids, moveTopic, function(err) { async.each(tids, moveTopic, function(err) {
if(!err) { if(!err) {
console.log('renaming ' + cid);
RDB.rename('temp_categories:' + cid + ':tid', 'categories:' + cid + ':tid'); RDB.rename('temp_categories:' + cid + ':tid', 'categories:' + cid + ':tid');
callback(null); callback(null);
} }
else else
callback(err); callback(err);
}); });
}); });
} else { } else {
console.log('category already upgraded '+ cid); winston.info('category already upgraded '+ cid);
callback(null); callback(null);
} }
}); });
} }
function upgradeUser(uid, callback) { function upgradeUser(uid, callback) {
RDB.hmgetObject('user:' + uid, ['joindate', 'postcount', 'reputation'], function(err, userData) { user.getUserFields(uid, ['joindate', 'postcount', 'reputation'], function(err, userData) {
if(err) if(err)
return callback(err); return callback(err);
RDB.zadd('users:joindate', userData.joindate, uid); RDB.zadd('users:joindate', userData.joindate, uid);
RDB.zadd('users:postcount', userData.postcount, uid); RDB.zadd('users:postcount', userData.postcount, uid);
RDB.zadd('users:reputation', userData.reputation, uid); RDB.zadd('users:reputation', userData.reputation, uid);
callback(null); callback(null);
}); });
} }
exports.upgrade = function() { exports.upgrade = function() {
console.log('upgrading nodebb now'); winston.info('upgrading nodebb now');
var schema = [ var schema = [
function upgradeCategories(next) { function upgradeCategories(next) {
console.log('upgrading categories'); winston.info('upgrading categories');
RDB.lrange('categories:cid', 0, -1, function(err, cids) { RDB.lrange('categories:cid', 0, -1, function(err, cids) {
async.each(cids, upgradeCategory, function(err) { async.each(cids, upgradeCategory, function(err) {
if(!err) if(!err) {
next(null, 'upgraded categories'); winston.info('upgraded categories');
else next(null, null);
} else {
next(err, null); next(err, null);
}
}); });
}); });
}, },
function upgradeUsers(next) { function upgradeUsers(next) {
console.log('upgrading users'); winston.info('upgrading users');
RDB.lrange('userlist', 0, -1, function(err, uids) { RDB.lrange('userlist', 0, -1, function(err, uids) {
async.each(uids, upgradeUser, function(err) { async.each(uids, upgradeUser, function(err) {
if(!err) if(!err) {
next(null, 'upgraded users'); winston.info('upgraded users')
else next(null, null);
} else {
next(err, null); next(err, null);
}); }
});
}); });
} }
]; ];
async.series(schema, function(err, results) { async.series(schema, function(err, results) {
if(!err) if(!err)
console.log('upgrade complete'); winston.info('upgrade complete');
else else
console.log(err); winston.err(err);
process.exit(); process.exit();
}); });
} }

View File

@@ -2,7 +2,8 @@ var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'), RDB = require('./redis.js'),
crypto = require('crypto'), crypto = require('crypto'),
emailjs = require('emailjs'), emailjs = require('emailjs'),
emailjsServer = emailjs.server.connect(config.mailer), meta = require('./meta.js'),
emailjsServer = emailjs.server.connect(meta.config.mailer),
bcrypt = require('bcrypt'), bcrypt = require('bcrypt'),
marked = require('marked'), marked = require('marked'),
notifications = require('./notifications.js'), notifications = require('./notifications.js'),
@@ -41,14 +42,14 @@ var utils = require('./../public/src/utils.js'),
} else next(); } else next();
} }
], function(err, results) { ], function(err, results) {
if (err) return callback(err, 0); // FIXME: Maintaining the 0 for backwards compatibility. Do we need this? if (err) return callback(err, null);
RDB.incr('global:next_user_id', function(err, uid) { RDB.incr('global:next_user_id', function(err, uid) {
RDB.handle(err); RDB.handle(err);
var gravatar = User.createGravatarURLFromEmail(email); var gravatar = User.createGravatarURLFromEmail(email);
var timestamp = Date.now(); var timestamp = Date.now();
RDB.hmset('user:'+uid, { RDB.hmset('user:'+uid, {
'uid': uid, 'uid': uid,
'username' : username, 'username' : username,
@@ -63,13 +64,15 @@ var utils = require('./../public/src/utils.js'),
'picture': gravatar, 'picture': gravatar,
'gravatarpicture' : gravatar, 'gravatarpicture' : gravatar,
'uploadedpicture': '', 'uploadedpicture': '',
'profileviews': 0,
'reputation': 0, 'reputation': 0,
'postcount': 0, 'postcount': 0,
'lastposttime': 0, 'lastposttime': 0,
'administrator': (uid == 1) ? 1 : 0, 'administrator': (uid == 1) ? 1 : 0,
'banned': 0,
'showemail': 0 'showemail': 0
}); });
RDB.set('username:' + username + ':uid', uid); RDB.set('username:' + username + ':uid', uid);
RDB.set('userslug:'+ userslug +':uid', uid); RDB.set('userslug:'+ userslug +':uid', uid);
@@ -87,7 +90,7 @@ var utils = require('./../public/src/utils.js'),
RDB.zadd('users:joindate', timestamp, uid); RDB.zadd('users:joindate', timestamp, uid);
RDB.zadd('users:postcount', 0, uid); RDB.zadd('users:postcount', 0, uid);
RDB.zadd('users:reputation', 0, uid); RDB.zadd('users:reputation', 0, uid);
io.sockets.emit('user.latest', {userslug: userslug, username: username}); io.sockets.emit('user.latest', {userslug: userslug, username: username});
if (password !== undefined) { if (password !== undefined) {
@@ -100,7 +103,7 @@ var utils = require('./../public/src/utils.js'),
}); });
}); });
}; };
User.delete = function(uid, callback) { User.delete = function(uid, callback) {
RDB.exists('user:'+uid, function(err, exists) { RDB.exists('user:'+uid, function(err, exists) {
if(exists === 1) { if(exists === 1) {
@@ -126,25 +129,21 @@ var utils = require('./../public/src/utils.js'),
} }
}); });
} }
User.ban = function(uid, callback) {
User.setUserField(uid, 'banned', 1, callback);
}
User.unban = function(uid, callback) {
User.setUserField(uid, 'banned', 0, callback);
}
User.getUserField = function(uid, field, callback) { User.getUserField = function(uid, field, callback) {
RDB.hget('user:' + uid, field, function(err, data) { RDB.hget('user:' + uid, field, callback);
if(err === null) {
callback(data);
} else {
console.log(err);
}
});
} }
User.getUserFields = function(uid, fields, callback) { User.getUserFields = function(uid, fields, callback) {
RDB.hmgetObject('user:' + uid, fields, function(err, data) { RDB.hmgetObject('user:' + uid, fields, callback);
if(err === null) {
callback(data);
} else {
console.log(err);
}
});
} }
User.getMultipleUserFields = function(uids, fields, callback) { User.getMultipleUserFields = function(uids, fields, callback) {
@@ -160,7 +159,9 @@ var utils = require('./../public/src/utils.js'),
}); });
function iterator(uid, callback) { function iterator(uid, callback) {
User.getUserFields(uid, fields, function(userData) { User.getUserFields(uid, fields, function(err, userData) {
if(err)
return callback(err);
returnData.push(userData); returnData.push(userData);
callback(null); callback(null);
}); });
@@ -190,6 +191,12 @@ var utils = require('./../public/src/utils.js'),
}); });
} }
User.filterBannedUsers = function(users) {
return users.filter(function(user) {
return (!user.banned || user.banned === '0');
});
}
User.updateProfile = function(uid, data, callback) { User.updateProfile = function(uid, data, callback) {
var fields = ['email', 'fullname', 'website', 'location', 'birthday', 'signature']; var fields = ['email', 'fullname', 'website', 'location', 'birthday', 'signature'];
@@ -199,30 +206,30 @@ var utils = require('./../public/src/utils.js'),
if(data['signature'] !== undefined && data['signature'].length > 150) { if(data['signature'] !== undefined && data['signature'].length > 150) {
next({error:'Signature can\'t be longer than 150 characters!'}, false); next({error:'Signature can\'t be longer than 150 characters!'}, false);
} else { } else {
next(null, true); next(null, true);
} }
} }
function isEmailAvailable(next) { function isEmailAvailable(next) {
if(!data['email']) { if(!data['email']) {
return next(null, true); return next(null, true);
} }
User.getUserField(uid, 'email', function(email) { User.getUserField(uid, 'email', function(err, email) {
if(email !== data['email']) { if(email !== data['email']) {
User.isEmailAvailable(data['email'], function(available) { User.isEmailAvailable(data['email'], function(available) {
if(!available) { if(!available) {
next({error:'Email not available!'}, false); next({error:'Email not available!'}, false);
} else { } else {
next(null, true); next(null, true);
} }
}); });
} else { } else {
next(null, true); next(null, true);
} }
}); });
} }
async.series([isSignatureValid, isEmailAvailable], function(err, results) { async.series([isSignatureValid, isEmailAvailable], function(err, results) {
if(err) { if(err) {
console.log(err); console.log(err);
@@ -245,8 +252,11 @@ var utils = require('./../public/src/utils.js'),
if(field === 'email') { if(field === 'email') {
var gravatarpicture = User.createGravatarURLFromEmail(data[field]); var gravatarpicture = User.createGravatarURLFromEmail(data[field]);
User.setUserField(uid, 'gravatarpicture', gravatarpicture); User.setUserField(uid, 'gravatarpicture', gravatarpicture);
User.getUserFields(uid, ['email', 'picture', 'uploadedpicture'], function(userData) { User.getUserFields(uid, ['email', 'picture', 'uploadedpicture'], function(err, userData) {
RDB.del('email:' + userData['email'] + ':uid'); if(err)
return callback(err);
RDB.del('email:' + userData['email'] + ':uid');
RDB.set('email:' + data['email'] + ':uid', uid); RDB.set('email:' + data['email'] + ':uid', uid);
User.setUserField(uid, field, data[field]); User.setUserField(uid, field, data[field]);
if(userData.picture !== userData.uploadedpicture) { if(userData.picture !== userData.uploadedpicture) {
@@ -259,9 +269,9 @@ var utils = require('./../public/src/utils.js'),
return; return;
} else if(field === 'signature') { } else if(field === 'signature') {
data[field] = utils.strip_tags(data[field]); data[field] = utils.strip_tags(data[field]);
} }
User.setUserField(uid, field, data[field]); User.setUserField(uid, field, data[field]);
callback(null); callback(null);
} else { } else {
@@ -285,10 +295,10 @@ var utils = require('./../public/src/utils.js'),
User.changePassword = function(uid, data, callback) { User.changePassword = function(uid, data, callback) {
if(!utils.isPasswordValid(data.newPassword)) { if(!utils.isPasswordValid(data.newPassword)) {
callback({err:'Invalid password!'}); callback({err:'Invalid password!'});
return; return;
} }
User.getUserField(uid, 'password', function(user_password) { User.getUserField(uid, 'password', function(err, user_password) {
bcrypt.compare(data.currentPassword, user_password, function(err, res) { bcrypt.compare(data.currentPassword, user_password, function(err, res) {
if(err) { if(err) {
console.log(err); console.log(err);
@@ -309,8 +319,8 @@ var utils = require('./../public/src/utils.js'),
}); });
} }
User.setUserField = function(uid, field, value) { User.setUserField = function(uid, field, value, callback) {
RDB.hset('user:' + uid, field, value); RDB.hset('user:' + uid, field, value, callback);
} }
User.setUserFields = function(uid, data) { User.setUserFields = function(uid, data) {
@@ -327,12 +337,12 @@ var utils = require('./../public/src/utils.js'),
User.getUsers = function(set, start, stop, callback) { User.getUsers = function(set, start, stop, callback) {
var data = []; var data = [];
RDB.zrevrange(set, start, stop, function(err, uids) { RDB.zrevrange(set, start, stop, function(err, uids) {
if(err) { if(err) {
return callback(err, null); return callback(err, null);
} }
function iterator(uid, callback) { function iterator(uid, callback) {
User.getUserData(uid, function(userData) { User.getUserData(uid, function(userData) {
if(userData) { if(userData) {
@@ -341,11 +351,10 @@ var utils = require('./../public/src/utils.js'),
callback(null); callback(null);
}); });
} }
async.eachSeries(uids, iterator, function(err) { async.eachSeries(uids, iterator, function(err) {
callback(err, data); callback(err, data);
}); });
}); });
} }
@@ -355,13 +364,13 @@ var utils = require('./../public/src/utils.js'),
default: 'identicon', default: 'identicon',
rating: 'pg' rating: 'pg'
}; };
if (!email) { if (!email) {
email = ''; email = '';
options.forcedefault = 'y'; options.forcedefault = 'y';
} }
return require('gravatar').url(email, options, https=global.nconf.get('https')); return require('gravatar').url(email, options, https=nconf.get('https'));
} }
User.hashPassword = function(password, callback) { User.hashPassword = function(password, callback) {
@@ -370,7 +379,7 @@ var utils = require('./../public/src/utils.js'),
return; return;
} }
bcrypt.genSalt(config.bcrypt_rounds, function(err, salt) { bcrypt.genSalt(nconf.get('bcrypt_rounds'), function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) { bcrypt.hash(password, salt, function(err, hash) {
callback(hash); callback(hash);
}); });
@@ -406,7 +415,7 @@ var utils = require('./../public/src/utils.js'),
User.incrementUserFieldBy(uid, 'postcount', 1, function(err, newpostcount) { User.incrementUserFieldBy(uid, 'postcount', 1, function(err, newpostcount) {
RDB.zadd('users:postcount', newpostcount, uid); RDB.zadd('users:postcount', newpostcount, uid);
}); });
User.setUserField(uid, 'lastposttime', timestamp); User.setUserField(uid, 'lastposttime', timestamp);
User.sendPostNotificationToFollowers(uid, tid, pid); User.sendPostNotificationToFollowers(uid, tid, pid);
@@ -435,9 +444,9 @@ var utils = require('./../public/src/utils.js'),
} }
User.sendConfirmationEmail = function (email) { User.sendConfirmationEmail = function (email) {
if (global.config['email:host'] && global.config['email:port'] && global.config['email:from']) { if (meta.config['email:host'] && meta.config['email:port'] && meta.config['email:from']) {
var confirm_code = utils.generateUUID(), var confirm_code = utils.generateUUID(),
confirm_link = config.url + 'confirm/' + confirm_code, confirm_link = nconf.get('url') + 'confirm/' + confirm_code,
confirm_email = global.templates['emails/header'] + global.templates['emails/email_confirm'].parse({'CONFIRM_LINK': confirm_link}) + global.templates['emails/footer'], confirm_email = global.templates['emails/header'] + global.templates['emails/email_confirm'].parse({'CONFIRM_LINK': confirm_link}) + global.templates['emails/footer'],
confirm_email_plaintext = global.templates['emails/email_confirm_plaintext'].parse({ 'CONFIRM_LINK': confirm_link }); confirm_email_plaintext = global.templates['emails/email_confirm_plaintext'].parse({ 'CONFIRM_LINK': confirm_link });
@@ -451,10 +460,10 @@ var utils = require('./../public/src/utils.js'),
RDB.set(confirm_key, email); RDB.set(confirm_key, email);
RDB.expire(confirm_key, expiry_time); RDB.expire(confirm_key, expiry_time);
// Send intro email w/ confirm code // Send intro email w/ confirm code
var message = emailjs.message.create({ var message = emailjs.message.create({
text: confirm_email_plaintext, text: confirm_email_plaintext,
from: config.mailer.from, from: meta.config.mailer.from,
to: email, to: email,
subject: '[NodeBB] Registration Email Verification', subject: '[NodeBB] Registration Email Verification',
attachment: [ attachment: [
@@ -562,19 +571,19 @@ var utils = require('./../public/src/utils.js'),
callback(null); callback(null);
}); });
} }
async.eachSeries(uids, iterator, function(err) { async.eachSeries(uids, iterator, function(err) {
callback(returnData); callback(returnData);
}); });
} }
User.sendPostNotificationToFollowers = function(uid, tid, pid) { User.sendPostNotificationToFollowers = function(uid, tid, pid) {
User.getUserField(uid, 'username', function(username) { User.getUserField(uid, 'username', function(err, username) {
RDB.smembers('followers:' + uid, function(err, followers) { RDB.smembers('followers:' + uid, function(err, followers) {
topics.getTopicField(tid, 'slug', function(err, slug) { topics.getTopicField(tid, 'slug', function(err, slug) {
var message = username + ' made a new post'; var message = username + ' made a new post';
notifications.create(message, 5, global.nconf.get('url') + 'topic/' + slug + '#' + pid, 'notification_'+ Date.now(), function(nid) { notifications.create(message, 5, nconf.get('url') + 'topic/' + slug + '#' + pid, 'notification_'+ Date.now(), function(nid) {
notifications.push(nid, followers); notifications.push(nid, followers);
}); });
}); });
@@ -610,8 +619,9 @@ var utils = require('./../public/src/utils.js'),
RDB.zrevrange('users:joindate', 0, 0, function(err, uid) { RDB.zrevrange('users:joindate', 0, 0, function(err, uid) {
RDB.handle(err); RDB.handle(err);
User.getUserFields(uid, ['username', 'userslug'], function(userData) { User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
socket.emit('user.latest', {userslug: userData.userslug, username: userData.username}); if(!err && userData)
socket.emit('user.latest', {userslug: userData.userslug, username: userData.username});
}); });
}); });
} }
@@ -642,14 +652,14 @@ var utils = require('./../public/src/utils.js'),
} }
function iterator(uid, callback) { function iterator(uid, callback) {
User.getUserField(uid, 'username', function(username) { User.getUserField(uid, 'username', function(err, username) {
usernames.push(username); usernames.push(username);
callback(null); callback(null);
}); });
} }
async.eachSeries(uids, iterator, function(err) { async.eachSeries(uids, iterator, function(err) {
callback(usernames); callback(usernames);
}); });
} }
@@ -661,14 +671,14 @@ var utils = require('./../public/src/utils.js'),
} }
function iterator(uid, callback) { function iterator(uid, callback) {
User.getUserField(uid, 'userslug', function(userslug) { User.getUserField(uid, 'userslug', function(err, userslug) {
userslugs.push(userslug); userslugs.push(userslug);
callback(null); callback(null);
}); });
} }
async.eachSeries(uids, iterator, function(err) { async.eachSeries(uids, iterator, function(err) {
callback(userslugs); callback(userslugs);
}); });
} }
@@ -777,6 +787,7 @@ var utils = require('./../public/src/utils.js'),
User.reset = { User.reset = {
validate: function(socket, code, callback) { validate: function(socket, code, callback) {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = null; callback = null;
} }
@@ -826,13 +837,13 @@ var utils = require('./../public/src/utils.js'),
RDB.set('reset:' + reset_code + ':uid', uid); RDB.set('reset:' + reset_code + ':uid', uid);
RDB.set('reset:' + reset_code + ':expiry', (60*60)+new Date()/1000|0); // Active for one hour RDB.set('reset:' + reset_code + ':expiry', (60*60)+new Date()/1000|0); // Active for one hour
var reset_link = config.url + 'reset/' + reset_code, var reset_link = nconf.get('url') + 'reset/' + reset_code,
reset_email = global.templates['emails/reset'].parse({'RESET_LINK': reset_link}), reset_email = global.templates['emails/reset'].parse({'RESET_LINK': reset_link}),
reset_email_plaintext = global.templates['emails/reset_plaintext'].parse({ 'RESET_LINK': reset_link }); reset_email_plaintext = global.templates['emails/reset_plaintext'].parse({ 'RESET_LINK': reset_link });
var message = emailjs.message.create({ var message = emailjs.message.create({
text: reset_email_plaintext, text: reset_email_plaintext,
from: config.mailer.from, from: meta.config.mailer?meta.config.mailer.from:'localhost@example.org',
to: email, to: email,
subject: 'Password Reset Requested', subject: 'Password Reset Requested',
attachment: [ attachment: [
@@ -869,14 +880,17 @@ var utils = require('./../public/src/utils.js'),
}); });
}, },
commit: function(socket, code, password) { commit: function(socket, code, password) {
this.validate(code, function(validated) { this.validate(socket, code, function(validated) {
if (validated) { if (validated) {
RDB.get('reset:' + code + ':uid', function(err, uid) { RDB.get('reset:' + code + ':uid', function(err, uid) {
if (err) { if (err) {
RDB.handle(err); RDB.handle(err);
} }
User.setUserField(uid, 'password', password); User.hashPassword(password, function(hash) {
User.setUserField(uid, 'password', hash);
});
RDB.del('reset:' + code + ':uid'); RDB.del('reset:' + code + ':uid');
RDB.del('reset:' + code + ':expiry'); RDB.del('reset:' + code + ':expiry');

View File

@@ -5,12 +5,12 @@ var express = require('express'),
RedisStore = require('connect-redis')(express), RedisStore = require('connect-redis')(express),
path = require('path'), path = require('path'),
redis = require('redis'), redis = require('redis'),
redisServer = redis.createClient(global.nconf.get('redis:port'), global.nconf.get('redis:host')), redisServer = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')),
marked = require('marked'), marked = require('marked'),
utils = require('../public/src/utils.js'), utils = require('../public/src/utils.js'),
pkg = require('../package.json'), pkg = require('../package.json'),
fs = require('fs'), fs = require('fs'),
user = require('./user.js'), user = require('./user.js'),
categories = require('./categories.js'), categories = require('./categories.js'),
posts = require('./posts.js'), posts = require('./posts.js'),
@@ -26,7 +26,7 @@ var express = require('express'),
(function(app) { (function(app) {
var templates = null; var templates = null;
/** /**
* `options` object requires: req, res * `options` object requires: req, res
* accepts: metaTags * accepts: metaTags
@@ -36,15 +36,15 @@ var express = require('express'),
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }, { name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
{ name: 'content-type', content: 'text/html; charset=UTF-8' }, { name: 'content-type', content: 'text/html; charset=UTF-8' },
{ name: 'apple-mobile-web-app-capable', content: 'yes' }, { name: 'apple-mobile-web-app-capable', content: 'yes' },
{ property: 'og:site_name', content: global.config.title || 'NodeBB' }, { property: 'og:site_name', content: meta.config.title || 'NodeBB' },
], ],
metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])), metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])),
templateValues = { templateValues = {
cssSrc: global.config['theme:src'] || global.nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css', cssSrc: meta.config['theme:src'] || nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css',
title: global.config['title'] || 'NodeBB', title: meta.config['title'] || 'NodeBB',
browserTitle: global.config['title'] || 'NodeBB', browserTitle: meta.config['title'] || 'NodeBB',
csrf: options.res.locals.csrf_token, csrf: options.res.locals.csrf_token,
relative_path: global.nconf.get('relative_path'), relative_path: nconf.get('relative_path'),
meta_tags: metaString meta_tags: metaString
}; };
@@ -60,7 +60,7 @@ var express = require('express'),
// Middlewares // Middlewares
app.use(express.favicon(path.join(__dirname, '../', 'public', 'favicon.ico'))); app.use(express.favicon(path.join(__dirname, '../', 'public', 'favicon.ico')));
app.use(require('less-middleware')({ src: path.join(__dirname, '../', 'public') })); app.use(require('less-middleware')({ src: path.join(__dirname, '../', 'public') }));
app.use(global.nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public'))); app.use(nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public')));
app.use(express.bodyParser()); // Puts POST vars in request.body app.use(express.bodyParser()); // Puts POST vars in request.body
app.use(express.cookieParser()); // If you want to parse cookies (res.cookies) app.use(express.cookieParser()); // If you want to parse cookies (res.cookies)
app.use(express.compress()); app.use(express.compress());
@@ -69,8 +69,11 @@ var express = require('express'),
client: redisServer, client: redisServer,
ttl: 60*60*24*14 ttl: 60*60*24*14
}), }),
secret: global.nconf.get('secret'), secret: nconf.get('secret'),
key: 'express.sid' key: 'express.sid',
cookie: {
maxAge: 60*60*24*30 // 30 days
}
})); }));
app.use(express.csrf()); app.use(express.csrf());
app.use(function(req, res, next) { app.use(function(req, res, next) {
@@ -83,11 +86,11 @@ var express = require('express'),
} }
auth.initialize(app); auth.initialize(app);
app.use(function(req, res, next) { app.use(function(req, res, next) {
global.nconf.set('https', req.secure); nconf.set('https', req.secure);
// Don't bother with session handling for API requests // Don't bother with session handling for API requests
if (/^\/api\//.test(req.url)) return next(); if (/^\/api\//.test(req.url)) return next();
@@ -100,7 +103,7 @@ var express = require('express'),
next(); next();
}); });
app.use(app.router); app.use(app.router);
app.use(function(req, res, next) { app.use(function(req, res, next) {
@@ -108,41 +111,40 @@ var express = require('express'),
// respond with html page // respond with html page
if (req.accepts('html')) { if (req.accepts('html')) {
//res.json('404', { url: req.url }); //res.json('404', { url: req.url });
res.redirect(global.nconf.get('relative_path') + '/404'); res.redirect(nconf.get('relative_path') + '/404');
return; return;
} }
// respond with json // respond with json
if (req.accepts('json')) { if (req.accepts('json')) {
console.log('sending 404 json');
res.send({ error: 'Not found' }); res.send({ error: 'Not found' });
return; return;
} }
// default to plain-text. send() // default to plain-text. send()
res.type('txt').send('Not found'); res.type('txt').send('Not found');
}); });
app.use(function(err, req, res, next) { app.use(function(err, req, res, next) {
// we may use properties of the error object // we may use properties of the error object
// here and next(err) appropriately, or if // here and next(err) appropriately, or if
// we possibly recovered from the error, simply next(). // we possibly recovered from the error, simply next().
console.error(err.stack); console.error(err.stack);
res.status(err.status || 500); res.status(err.status || 500);
res.json('500', { error: err.message }); res.json('500', { error: err.message });
}); });
app.create_route = function(url, tpl) { // to remove app.create_route = function(url, tpl) { // to remove
return '<script>templates.ready(function(){ajaxify.go("' + url + '", null, "' + tpl + '");});</script>'; return '<script>templates.ready(function(){ajaxify.go("' + url + '", null, "' + tpl + '");});</script>';
}; };
app.namespace(global.nconf.get('relative_path'), function() {
app.namespace(nconf.get('relative_path'), function() {
auth.create_routes(app); auth.create_routes(app);
admin.create_routes(app); admin.create_routes(app);
@@ -150,20 +152,20 @@ var express = require('express'),
installRoute.create_routes(app); installRoute.create_routes(app);
testBed.create_routes(app); testBed.create_routes(app);
apiRoute.create_routes(app); apiRoute.create_routes(app);
// Basic Routes (entirely client-side parsed, goal is to move the rest of the crap in this file into this one section) // Basic Routes (entirely client-side parsed, goal is to move the rest of the crap in this file into this one section)
(function() { (function() {
var routes = ['login', 'register', 'account', 'recent', 'unread', 'popular', 'active', '403', '404']; var routes = ['login', 'register', 'account', 'recent', 'unread', 'popular', 'active', '403', '404'];
for (var i=0, ii=routes.length; i<ii; i++) { for (var i=0, ii=routes.length; i<ii; i++) {
(function(route) { (function(route) {
app.get('/' + route, function(req, res) { app.get('/' + route, function(req, res) {
if ((route === 'login' || route ==='register') && (req.user && req.user.uid > 0)) { if ((route === 'login' || route ==='register') && (req.user && req.user.uid > 0)) {
user.getUserField(req.user.uid, 'userslug', function(userslug) { user.getUserField(req.user.uid, 'userslug', function(err, userslug) {
res.redirect('/users/'+userslug); res.redirect('/users/'+userslug);
}); });
return; return;
} }
@@ -175,7 +177,7 @@ var express = require('express'),
}(routes[i])); }(routes[i]));
} }
}()); }());
app.get('/', function(req, res) { app.get('/', function(req, res) {
async.parallel({ async.parallel({
@@ -184,9 +186,9 @@ var express = require('express'),
req: req, req: req,
res: res, res: res,
metaTags: [ metaTags: [
{ name: "title", content: global.config.title || 'NodeBB' }, { name: "title", content: meta.config.title || 'NodeBB' },
{ name: "description", content: global.config.description || '' }, { name: "description", content: meta.config.description || '' },
{ property: 'og:title', content: 'Index | ' + (global.config.title || 'NodeBB') }, { property: 'og:title', content: 'Index | ' + (meta.config.title || 'NodeBB') },
{ property: "og:type", content: 'website' } { property: "og:type", content: 'website' }
] ]
}, next); }, next);
@@ -205,9 +207,10 @@ var express = require('express'),
); );
}) })
}); });
app.get('/topic/:topic_id/:slug?', function(req, res) { app.get('/topic/:topic_id/:slug?', function(req, res) {
var tid = req.params.topic_id; var tid = req.params.topic_id;
if (tid.match(/^\d+\.rss$/)) { if (tid.match(/^\d+\.rss$/)) {
fs.readFile('feeds/topics/' + tid, function (err, data) { fs.readFile('feeds/topics/' + tid, function (err, data) {
@@ -215,7 +218,7 @@ var express = require('express'),
res.type('text').send(404, "Unable to locate an rss feed at this location."); res.type('text').send(404, "Unable to locate an rss feed at this location.");
return; return;
} }
res.type('xml').set('Content-Length', data.length).send(data); res.type('xml').set('Content-Length', data.length).send(data);
}); });
return; return;
@@ -224,13 +227,19 @@ var express = require('express'),
async.waterfall([ async.waterfall([
function(next) { function(next) {
topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), function(err, topicData) { topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), function(err, topicData) {
if(topicData) {
if(topicData.deleted === '1' && topicData.expose_tools === 0)
return next(new Error('Topic deleted'), null);
}
next(err, topicData); next(err, topicData);
}); });
}, },
function(topicData, next) { function(topicData, next) {
var posts = topicData.posts.push(topicData.main_posts[0]), var posts = topicData.posts.push(topicData.main_posts[0]),
lastMod = 0, lastMod = 0,
timestamp; timestamp;
for(var x=0,numPosts=topicData.posts.length;x<numPosts;x++) { for(var x=0,numPosts=topicData.posts.length;x<numPosts;x++) {
timestamp = parseInt(topicData.posts[x].timestamp, 10); timestamp = parseInt(topicData.posts[x].timestamp, 10);
if (timestamp > lastMod) lastMod = timestamp; if (timestamp > lastMod) lastMod = timestamp;
@@ -241,9 +250,9 @@ var express = require('express'),
res: res, res: res,
metaTags: [ metaTags: [
{ name: "title", content: topicData.topic_name }, { name: "title", content: topicData.topic_name },
{ property: 'og:title', content: topicData.topic_name + ' | ' + (global.config.title || 'NodeBB') }, { property: 'og:title', content: topicData.topic_name + ' | ' + (meta.config.title || 'NodeBB') },
{ property: "og:type", content: 'article' }, { property: "og:type", content: 'article' },
{ property: "og:url", content: global.nconf.get('url') + 'topic/' + topicData.slug }, { property: "og:url", content: nconf.get('url') + 'topic/' + topicData.slug },
{ property: 'og:image', content: topicData.main_posts[0].picture }, { property: 'og:image', content: topicData.main_posts[0].picture },
{ property: "article:published_time", content: new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toISOString() }, { property: "article:published_time", content: new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toISOString() },
{ property: 'article:modified_time', content: new Date(lastMod).toISOString() }, { property: 'article:modified_time', content: new Date(lastMod).toISOString() },
@@ -271,7 +280,7 @@ var express = require('express'),
app.get('/category/:category_id/:slug?', function(req, res) { app.get('/category/:category_id/:slug?', function(req, res) {
var cid = req.params.category_id; var cid = req.params.category_id;
if (cid.match(/^\d+\.rss$/)) { if (cid.match(/^\d+\.rss$/)) {
fs.readFile('feeds/categories/' + cid, function (err, data) { fs.readFile('feeds/categories/' + cid, function (err, data) {
if (err) { if (err) {
@@ -338,7 +347,7 @@ var express = require('express'),
res.send( "User-agent: *\n" + res.send( "User-agent: *\n" +
"Disallow: \n" + "Disallow: \n" +
"Disallow: /admin/\n" + "Disallow: /admin/\n" +
"Sitemap: " + global.nconf.get('url') + "sitemap.xml"); "Sitemap: " + nconf.get('url') + "sitemap.xml");
}); });
app.get('/cid/:cid', function(req, res) { app.get('/cid/:cid', function(req, res) {
@@ -374,24 +383,24 @@ var express = require('express'),
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send( res.send(
header + header +
'\n\t<script>templates.ready(function(){ajaxify.go("outgoing?url=' + req.query.url + '");});</script>' + '\n\t<script>templates.ready(function(){ajaxify.go("outgoing?url=' + encodeURIComponent(req.query.url) + '", null, null, true);});</script>' +
templates['footer'] templates['footer']
); );
}); });
}); });
app.get('/search', function(req, res) { app.get('/search', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("search", null, "search") + templates['footer']); res.send(header + app.create_route("search", null, "search") + templates['footer']);
}); });
}); });
app.get('/search/:term', function(req, res) { app.get('/search/:term', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) { app.build_header({ 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']);
}); });
}); });
app.get('/reindex', function(req, res) { app.get('/reindex', function(req, res) {
topics.reIndexAll(function(err) { topics.reIndexAll(function(err) {
if(err) { if(err) {
@@ -401,7 +410,7 @@ var express = require('express'),
} }
}); });
}); });
}); });
}(WebServer)); }(WebServer));

View File

@@ -16,18 +16,19 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
async = require('async'), async = require('async'),
RedisStoreLib = require('connect-redis')(express), RedisStoreLib = require('connect-redis')(express),
redis = require('redis'), redis = require('redis'),
redisServer = redis.createClient(global.nconf.get('redis:port'), global.nconf.get('redis:host')), redisServer = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')),
RedisStore = new RedisStoreLib({ RedisStore = new RedisStoreLib({
client: redisServer, client: redisServer,
ttl: 60*60*24*14 ttl: 60*60*24*14
}), }),
socketCookieParser = express.cookieParser(global.nconf.get('secret')), socketCookieParser = express.cookieParser(nconf.get('secret')),
admin = { admin = {
'categories': require('./admin/categories.js'), 'categories': require('./admin/categories.js'),
'user': require('./admin/user.js') 'user': require('./admin/user.js')
}, },
plugins = require('./plugins'); plugins = require('./plugins'),
winston = require('winston');
(function(io) { (function(io) {
var users = {}, var users = {},
userSockets = {}, userSockets = {},
@@ -48,22 +49,22 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
userSockets[uid] = userSockets[uid] || []; userSockets[uid] = userSockets[uid] || [];
userSockets[uid].push(socket); userSockets[uid].push(socket);
if(uid) { if(uid) {
socket.join('uid_' + uid); socket.join('uid_' + uid);
io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid)); io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid));
user.getUserField(uid, 'username', function(username) { user.getUserField(uid, 'username', function(err, username) {
socket.emit('event:connect', {status: 1, username:username}); socket.emit('event:connect', {status: 1, username:username});
}); });
} }
}); });
}); });
socket.on('disconnect', function() { socket.on('disconnect', function() {
var index = userSockets[uid].indexOf(socket); var index = userSockets[uid].indexOf(socket);
if(index !== -1) { if(index !== -1) {
userSockets[uid].splice(index, 1); userSockets[uid].splice(index, 1);
@@ -73,17 +74,17 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
delete users[sessionID]; delete users[sessionID];
if(uid) if(uid)
io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid)); io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid));
} }
for(var roomName in rooms) { for(var roomName in rooms) {
socket.leave(roomName); socket.leave(roomName);
if(rooms[roomName][socket.id]) { if(rooms[roomName][socket.id]) {
delete rooms[roomName][socket.id]; delete rooms[roomName][socket.id];
} }
updateRoomBrowsingText(roomName); updateRoomBrowsingText(roomName);
} }
}); });
@@ -105,7 +106,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
function getAnonymousCount(roomName) { function getAnonymousCount(roomName) {
var clients = io.sockets.clients(roomName); var clients = io.sockets.clients(roomName);
var anonCount = 0; var anonCount = 0;
for(var i=0; i<clients.length; ++i) { for(var i=0; i<clients.length; ++i) {
var hs = clients[i].handshake; var hs = clients[i].handshake;
@@ -113,11 +114,11 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
++anonCount; ++anonCount;
} }
} }
return anonCount; return anonCount;
} }
var uids = getUidsInRoom(rooms[roomName]); var uids = getUidsInRoom(rooms[roomName]);
var anonymousCount = getAnonymousCount(roomName); var anonymousCount = getAnonymousCount(roomName);
function userList(users, anonymousCount, userCount) { function userList(users, anonymousCount, userCount) {
@@ -126,8 +127,8 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
for (var i = 0, ii=users.length; i<ii; ++i) { for (var i = 0, ii=users.length; i<ii; ++i) {
usernames[i] = '<strong>' + '<a href="/users/'+users[i].userslug+'">' + users[i].username + '</a></strong>'; usernames[i] = '<strong>' + '<a href="/users/'+users[i].userslug+'">' + users[i].username + '</a></strong>';
} }
var joiner = anonymousCount + userCount == 1 ? 'is' : 'are', var joiner = anonymousCount + userCount == 1 ? 'is' : 'are',
userList = anonymousCount > 0 ? usernames.concat(util.format('%d guest%s', anonymousCount, anonymousCount > 1 ? 's' : '')) : usernames, userList = anonymousCount > 0 ? usernames.concat(util.format('%d guest%s', anonymousCount, anonymousCount > 1 ? 's' : '')) : usernames,
lastUser = userList.length > 1 ? ' and ' + userList.pop() : ''; lastUser = userList.length > 1 ? ' and ' + userList.pop() : '';
@@ -145,18 +146,18 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
} }
socket.on('event:enter_room', function(data) { socket.on('event:enter_room', function(data) {
if (data.leave !== null) { if (data.leave !== null) {
socket.leave(data.leave); socket.leave(data.leave);
} }
socket.join(data.enter); socket.join(data.enter);
rooms[data.enter] = rooms[data.enter] || {}; rooms[data.enter] = rooms[data.enter] || {};
if (uid) { if (uid) {
rooms[data.enter][socket.id] = uid; rooms[data.enter][socket.id] = uid;
if (data.leave && rooms[data.leave] && rooms[data.leave][socket.id]) { if (data.leave && rooms[data.leave] && rooms[data.leave][socket.id]) {
delete rooms[data.leave][socket.id]; delete rooms[data.leave][socket.id];
} }
@@ -167,18 +168,20 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
updateRoomBrowsingText(data.enter); updateRoomBrowsingText(data.enter);
if (data.enter != 'admin') if (data.enter != 'admin')
io.sockets.in('admin').emit('api:get_all_rooms', io.sockets.manager.rooms); io.sockets.in('admin').emit('api:get_all_rooms', io.sockets.manager.rooms);
}); });
// BEGIN: API calls (todo: organize) // BEGIN: API calls (todo: organize)
socket.on('api:updateHeader', function(data) { socket.on('api:updateHeader', function(data) {
if(uid) { if(uid) {
user.getUserFields(uid, data.fields, function(fields) { user.getUserFields(uid, data.fields, function(err, fields) {
fields.uid = uid; if(!err && fields) {
socket.emit('api:updateHeader', fields); fields.uid = uid;
socket.emit('api:updateHeader', fields);
}
}); });
} }
else { else {
@@ -186,12 +189,12 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
uid:0, uid:0,
username: "Anonymous User", username: "Anonymous User",
email: '', email: '',
picture: require('gravatar').url('', {s:'24'}, https=global.nconf.get('https')) picture: require('gravatar').url('', {s:'24'}, https=nconf.get('https'))
}); });
} }
}); });
socket.on('user.exists', function(data) { socket.on('user.exists', function(data) {
user.exists(utils.slugify(data.username), function(exists){ user.exists(utils.slugify(data.username), function(exists){
socket.emit('user.exists', {exists: exists}); socket.emit('user.exists', {exists: exists});
@@ -232,12 +235,12 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:user.get_online_users', function(data) { socket.on('api:user.get_online_users', function(data) {
var returnData = []; var returnData = [];
for(var i=0; i<data.length; ++i) { for(var i=0; i<data.length; ++i) {
var uid = data[i]; var uid = data[i];
if(isUserOnline(uid)) if(isUserOnline(uid))
returnData.push(uid); returnData.push(uid);
else else
returnData.push(0); returnData.push(0);
} }
socket.emit('api:user.get_online_users', returnData); socket.emit('api:user.get_online_users', returnData);
@@ -250,30 +253,34 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:user.changePassword', function(data, callback) { socket.on('api:user.changePassword', function(data, callback) {
user.changePassword(uid, data, callback); user.changePassword(uid, data, callback);
}); });
socket.on('api:user.updateProfile', function(data, callback) { socket.on('api:user.updateProfile', function(data, callback) {
user.updateProfile(uid, data, callback); user.updateProfile(uid, data, callback);
}); });
socket.on('api:user.changePicture', function(data, callback) { socket.on('api:user.changePicture', function(data, callback) {
var type = data.type; var type = data.type;
function updateHeader() { function updateHeader() {
user.getUserFields(uid, ['picture'], function(fields) { user.getUserFields(uid, ['picture'], function(err, fields) {
fields.uid = uid; if(!err && fields) {
socket.emit('api:updateHeader', fields); fields.uid = uid;
callback(true); socket.emit('api:updateHeader', fields);
callback(true);
} else {
callback(false);
}
}); });
} }
if(type === 'gravatar') { if(type === 'gravatar') {
user.getUserField(uid, 'gravatarpicture', function(gravatar) { user.getUserField(uid, 'gravatarpicture', function(err, gravatar) {
user.setUserField(uid, 'picture', gravatar); user.setUserField(uid, 'picture', gravatar);
updateHeader(); updateHeader();
}); });
} else if(type === 'uploaded') { } else if(type === 'uploaded') {
user.getUserField(uid, 'uploadedpicture', function(uploadedpicture) { user.getUserField(uid, 'uploadedpicture', function(err, uploadedpicture) {
user.setUserField(uid, 'picture', uploadedpicture); user.setUserField(uid, 'picture', uploadedpicture);
updateHeader(); updateHeader();
}); });
@@ -283,7 +290,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}); });
socket.on('api:user.follow', function(data, callback) { socket.on('api:user.follow', function(data, callback) {
if(uid) { if(uid) {
user.follow(uid, data.uid, callback); user.follow(uid, data.uid, callback);
} }
}); });
@@ -326,21 +333,21 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
} }
return; return;
} }
if(result) { if(result) {
posts.getTopicPostStats(socket); posts.getTopicPostStats(socket);
socket.emit('event:alert', { socket.emit('event:alert', {
title: 'Thank you for posting', title: 'Thank you for posting',
message: 'You have successfully posted. Click here to view your post.', message: 'You have successfully posted. Click here to view your post.',
type: 'notify', type: 'notify',
timeout: 2000 timeout: 2000
}); });
} }
}); });
}); });
socket.on('api:topics.markAllRead', function(data, callback) { socket.on('api:topics.markAllRead', function(data, callback) {
topics.markAllRead(uid, function(err, success) { topics.markAllRead(uid, function(err, success) {
if(!err && success) { if(!err && success) {
@@ -361,7 +368,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}); });
return; return;
} }
posts.reply(data.topic_id, uid, data.content, data.images, function(err, result) { posts.reply(data.topic_id, uid, data.content, data.images, function(err, result) {
if(err) { if(err) {
if(err.message === 'content-too-short') { if(err.message === 'content-too-short') {
@@ -378,20 +385,20 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
} }
return; return;
} }
if(result) { if(result) {
posts.getTopicPostStats(socket); posts.getTopicPostStats(socket);
socket.emit('event:alert', { socket.emit('event:alert', {
title: 'Reply Successful', title: 'Reply Successful',
message: 'You have successfully replied. Click here to view your reply.', message: 'You have successfully replied. Click here to view your reply.',
type: 'notify', type: 'notify',
timeout: 2000 timeout: 2000
}); });
} }
}); });
}); });
@@ -477,9 +484,9 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
postTools.restore(uid, data.pid); postTools.restore(uid, data.pid);
}); });
socket.on('api:notifications.get', function(data) { socket.on('api:notifications.get', function(data, callback) {
user.notifications.get(uid, function(notifs) { user.notifications.get(uid, function(notifs) {
socket.emit('api:notifications.get', notifs); callback(notifs);
}); });
}); });
@@ -499,44 +506,61 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}); });
}); });
socket.on('getChatMessages', function(data, callback) {
var touid = data.touid;
require('./messaging').getMessages(uid, touid, function(err, messages) {
if(err)
return callback(null);
callback(messages);
});
});
socket.on('sendChatMessage', function(data) { socket.on('sendChatMessage', function(data) {
var touid = data.touid; var touid = data.touid;
if(touid === uid || uid === 0) {
return;
}
if(userSockets[touid]) { if(userSockets[touid]) {
var msg = utils.strip_tags(data.message), var msg = utils.strip_tags(data.message),
numSockets = userSockets[touid].length; numSockets = userSockets[touid].length;
user.getUserField(uid, 'username', function(username) { user.getUserField(uid, 'username', function(err, username) {
var finalMessage = username + ' says : ' + msg; var finalMessage = username + ' says : ' + msg;
for(var x=0;x<numSockets;x++) { for(var x=0;x<numSockets;x++) {
userSockets[touid][x].emit('chatMessage', {fromuid:uid, username:username, message:finalMessage}); userSockets[touid][x].emit('chatMessage', {fromuid:uid, username:username, message:finalMessage});
} }
notifications.create(finalMessage, 5, '#', 'notification_'+uid+'_'+touid, function(nid) { notifications.create(finalMessage, 5, '#', 'notification_' + uid + '_' + touid, function(nid) {
notifications.push(nid, [touid], function(success) { notifications.push(nid, [touid], function(success) {
}); });
}); });
require('./messaging').addMessage(uid, touid, msg, function(err, message) {
});
}); });
} }
}); });
socket.on('api:config.get', function(data) { socket.on('api:config.get', function(data) {
meta.config.get(function(config) { meta.configs.get(function(config) {
socket.emit('api:config.get', config); socket.emit('api:config.get', config);
}); });
}); });
socket.on('api:config.set', function(data) { socket.on('api:config.set', function(data) {
meta.config.set(data.key, data.value, function(err) { meta.configs.set(data.key, data.value, function(err) {
if (!err) socket.emit('api:config.set', { status: 'ok' }); if (!err) socket.emit('api:config.set', { status: 'ok' });
}); });
}); });
socket.on('api:config.remove', function(key) { socket.on('api:config.remove', function(key) {
meta.config.remove(key); meta.configs.remove(key);
}); });
socket.on('api:composer.push', function(data) { socket.on('api:composer.push', function(data) {
@@ -544,7 +568,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
if (parseInt(data.tid) > 0) { if (parseInt(data.tid) > 0) {
topics.getTopicData(data.tid, function(topicData) { topics.getTopicData(data.tid, function(topicData) {
if (data.body) if (data.body)
topicData.body = data.body; topicData.body = data.body;
socket.emit('api:composer.push', { socket.emit('api:composer.push', {
@@ -554,14 +578,16 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}); });
}); });
} else if (parseInt(data.cid) > 0) { } else if (parseInt(data.cid) > 0) {
user.getUserFields(uid, ['username', 'picture'], function(userData) { user.getUserFields(uid, ['username', 'picture'], function(err, userData) {
socket.emit('api:composer.push', { if(!err && userData) {
tid: 0, socket.emit('api:composer.push', {
cid: data.cid, tid: 0,
username: userData.username, cid: data.cid,
picture: userData.picture, username: userData.username,
title: undefined picture: userData.picture,
}); title: undefined
});
}
}); });
} else if (parseInt(data.pid) > 0) { } else if (parseInt(data.pid) > 0) {
async.parallel([ async.parallel([
@@ -627,18 +653,18 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}); });
socket.on('api:topic.loadMore', function(data, callback) { socket.on('api:topic.loadMore', function(data, callback) {
var start = data.after, var start = data.after,
end = start + 9; end = start + 9;
topics.getTopicPosts(data.tid, start, end, uid, function(posts) { topics.getTopicPosts(data.tid, start, end, uid, function(posts) {
callback({posts:posts}); callback({posts:posts});
}); });
}); });
socket.on('api:category.loadMore', function(data, callback) { socket.on('api:category.loadMore', function(data, callback) {
var start = data.after, var start = data.after,
end = start + 9; end = start + 9;
categories.getCategoryTopics(data.cid, start, end, uid, function(topics) { categories.getCategoryTopics(data.cid, start, end, uid, function(topics) {
callback({topics:topics}); callback({topics:topics});
}); });
@@ -652,11 +678,11 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
callback(latestTopics); callback(latestTopics);
}); });
}); });
socket.on('api:topics.loadMoreUnreadTopics', function(data, callback) { socket.on('api:topics.loadMoreUnreadTopics', function(data, callback) {
var start = data.after, var start = data.after,
end = start + 9; end = start + 9;
topics.getUnreadTopics(uid, start, end, function(unreadTopics) { topics.getUnreadTopics(uid, start, end, function(unreadTopics) {
callback(unreadTopics); callback(unreadTopics);
}); });
@@ -665,14 +691,14 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:users.loadMore', function(data, callback) { socket.on('api:users.loadMore', function(data, callback) {
var start = data.after, var start = data.after,
end = start + 19; end = start + 19;
user.getUsers(data.set, start, end, function(err, data) { user.getUsers(data.set, start, end, function(err, data) {
if(err) { if(err) {
console.log(err); winston.err(err);
} else { } else {
callback({users:data}); callback({users:data});
} }
}); });
}); });
socket.on('api:admin.topics.getMore', function(data, callback) { socket.on('api:admin.topics.getMore', function(data, callback) {
@@ -684,13 +710,13 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:admin.categories.update', function(data) { socket.on('api:admin.categories.update', function(data) {
admin.categories.update(data, socket); admin.categories.update(data, socket);
}); });
socket.on('api:admin.user.makeAdmin', function(theirid) { socket.on('api:admin.user.makeAdmin', function(theirid) {
if(uid && uid > 0) { if(uid && uid > 0) {
admin.user.makeAdmin(uid, theirid, socket); admin.user.makeAdmin(uid, theirid, socket);
} }
}); });
socket.on('api:admin.user.removeAdmin', function(theirid) { socket.on('api:admin.user.removeAdmin', function(theirid) {
if(uid && uid > 0) { if(uid && uid > 0) {
admin.user.removeAdmin(uid, theirid, socket); admin.user.removeAdmin(uid, theirid, socket);
@@ -703,14 +729,26 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
} }
}); });
socket.on('api:admin.user.banUser', function(theirid) {
if(uid && uid > 0) {
admin.user.banUser(uid, theirid, socket);
}
});
socket.on('api:admin.user.unbanUser', function(theirid) {
if(uid && uid > 0) {
admin.user.unbanUser(uid, theirid, socket);
}
});
socket.on('api:admin.user.search', function(username) { socket.on('api:admin.user.search', function(username) {
if(uid && uid > 0) { if(uid && uid > 0) {
user.search(username, function(data) { user.search(username, function(data) {
socket.emit('api:admin.user.search', data); socket.emit('api:admin.user.search', data);
}); });
} } else {
else
socket.emit('api:admin.user.search', null); socket.emit('api:admin.user.search', null);
}
}); });
socket.on('api:admin.themes.getInstalled', function(callback) { socket.on('api:admin.themes.getInstalled', function(callback) {
@@ -726,10 +764,10 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}); });
socket.on('api:meta.buildTitle', function(text, callback) { socket.on('api:meta.buildTitle', function(text, callback) {
meta.title.build(text, uid, function(err, title) { meta.title.build(text, uid, function(err, title, numNotifications) {
callback(title); callback(title, numNotifications);
}); });
}); });
}); });
}(SocketIO)); }(SocketIO));