Compare commits

..

162 Commits

Author SHA1 Message Date
Julian Lam
0ad9e84042 publishing 0.0.4 2013-07-25 16:35:10 -04:00
Baris Usakli
8f4d4664e4 changed class 2013-07-25 16:33:06 -04:00
Baris Usakli
6e5420fd64 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-25 16:20:28 -04:00
Baris Usakli
19b570cdcf closes #122 2013-07-25 16:20:18 -04:00
Julian Lam
eabba1cc1f swapped position of reply/quote button in main post in topic (#95) 2013-07-25 15:53:25 -04:00
Julian Lam
cb8c8f9e42 tweaked OG tags for topics 2013-07-25 15:49:55 -04:00
Julian Lam
7dbf18c1d0 removing authors from OG tags 2013-07-25 15:44:54 -04:00
Julian Lam
56dcdd0378 Merge branch 'master' of github.com:psychobunny/node-forum 2013-07-25 15:34:33 -04:00
Julian Lam
fe286870a3 closed #116 2013-07-25 15:34:22 -04:00
Baris Usakli
58ad74abb5 send new url when we are done 2013-07-25 15:26:35 -04:00
Baris Usakli
9160a135a9 changed to crop 2013-07-25 15:19:44 -04:00
Baris Usakli
6420e4c6a6 added this back, file extensions might change need to delete old pic 2013-07-25 14:51:31 -04:00
Baris Usakli
fa9c0e020a Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-25 14:44:52 -04:00
Baris Usakli
433c7d91eb some fixes 2013-07-25 14:44:43 -04:00
Julian Lam
2c95c19c9a Merge branch 'master' of github.com:psychobunny/node-forum 2013-07-25 14:37:14 -04:00
Julian Lam
c5f6a0d668 closed #95 2013-07-25 14:37:08 -04:00
Baris Usakli
3100bfcfd3 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-25 14:30:17 -04:00
Baris Usakli
472f40479e privacy page 2013-07-25 14:30:12 -04:00
Julian Lam
12e8423a24 closed #132 - issue where all auth routes were not rendering the header properly 2013-07-25 14:04:00 -04:00
Julian Lam
336345ee83 closed #137 2013-07-25 13:18:55 -04:00
Julian Lam
a2f46b9e59 updated app name (lowercase only) and added missing dep request 2013-07-25 13:09:11 -04:00
Julian Lam
1752be237b minor style changes to the composer (moved buttons to the bottom as well) 2013-07-25 12:36:43 -04:00
Baris Usakli
4260930523 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-25 12:31:03 -04:00
Baris Usakli
6af3b0ffc6 account settings 2013-07-25 12:30:59 -04:00
Julian Lam
53d001a751 Merge branch 'master' of github.com:psychobunny/node-forum 2013-07-25 12:30:46 -04:00
Julian Lam
5caf258238 fixing build_header refactor for user routes 2013-07-25 12:30:31 -04:00
Baris Usakli
5df7af6075 reset reconnect tries to 0 after a succesfull reconnect 2013-07-25 12:05:19 -04:00
Julian Lam
ce769a87ef refactored parallel to waterfall in topic and category routes (to allow
for better title generation)
2013-07-24 19:27:25 -04:00
Julian Lam
931beecc21 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-24 18:54:47 -04:00
Julian Lam
4588745b9a refactored all calls to app.build_header (in regular and auth routes) 2013-07-24 18:54:14 -04:00
Baris Soner Usakli
12d31517b0 removed console.log 2013-07-24 17:47:26 -04:00
Baris Soner Usakli
08e3eac008 closes #120 2013-07-24 16:29:17 -04:00
Baris Soner Usakli
5b349dbfaa more css 2013-07-24 15:46:18 -04:00
Baris Soner Usakli
370d931bc7 some css changes #95 2013-07-24 15:31:56 -04:00
Julian Lam
d6938f4818 first pass at a custom title function (not ready for prod) 2013-07-24 15:19:26 -04:00
Baris Soner Usakli
72a0082a28 admin user page fix, added routers for admin/users 2013-07-24 15:18:31 -04:00
Baris Soner Usakli
6865eb9f0f changed the drag and drop image upload, closes #108 2013-07-24 15:03:49 -04:00
Julian Lam
1f8f61d1d8 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-24 13:19:49 -04:00
Julian Lam
83b0b6a523 fixing issue with md parser 2013-07-24 13:19:36 -04:00
Baris Soner Usakli
744e4579b7 setUserFields 2013-07-24 13:12:56 -04:00
Baris Soner Usakli
2f00640b6b Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-24 12:57:44 -04:00
Baris Soner Usakli
cacc732015 fixed filename of first uploaded image, added a setUserFields method to user.js 2013-07-24 12:57:36 -04:00
Julian Lam
33e676b033 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-24 12:53:10 -04:00
Julian Lam
bea5dff563 fixed #31 - twitter profile picture now used as nodebb avatar in lieu of
email address for gravatar
2013-07-24 12:52:16 -04:00
Baris Soner Usakli
f7217f2d12 closes #130 2013-07-24 12:33:37 -04:00
Julian Lam
762cecf1c3 fixed #129 2013-07-24 12:04:32 -04:00
Julian Lam
42d1cade23 fixing websockets crash on server restart 2013-07-24 11:39:29 -04:00
Julian Lam
a990cf3e0d added missing "return" when a user creation doesn't pass all server-side
checks...
2013-07-24 11:26:49 -04:00
Julian Lam
10a42d4e08 minor var renaming 2013-07-24 11:25:14 -04:00
Julian Lam
24c235e360 fixed #128 - regression caused by removal of 'connect' package 2013-07-24 09:16:10 -04:00
Julian Lam
b5ecb9a762 removing dependency on package 'connect' 2013-07-24 01:43:39 -04:00
Julian Lam
ff065dcc2f Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-24 00:06:07 -04:00
Julian Lam
c5c6bcec4b closed #119 2013-07-24 00:05:58 -04:00
Julian Lam
6a12ecf8e7 Merge branch 'master' of github.com:psychobunny/node-forum 2013-07-23 17:22:26 -04:00
Julian Lam
cc1cc7cae4 first pass at meta tag construction abstraction (issue #116). Also addressed issue #118 2013-07-23 17:21:44 -04:00
Baris Soner Usakli
6e0c84f9e5 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-23 17:19:29 -04:00
Baris Soner Usakli
e7c9079449 anons cant follow, hide follow button if not logged in 2013-07-23 17:19:19 -04:00
Julian Lam
59029a0ef0 minor tweaks to theme engine 2013-07-23 16:43:02 -04:00
Baris Soner Usakli
bf6a38904a skip loading a topic if cant load its data 2013-07-23 14:44:13 -04:00
Baris Soner Usakli
6ab37bde09 closes #105 2013-07-23 13:17:54 -04:00
Baris Soner Usakli
8947553ecd closes #117 2013-07-23 12:35:45 -04:00
psychobunny
fe996f75b6 replaced the ascii > with the right chevron icon
http://fortawesome.github.io/Font-Awesome/icon/chevron-right/
2013-07-23 16:33:24 +08:00
psychobunny
45cb24fb53 UI: fixed recent replies in category sidebar 2013-07-23 16:18:56 +08:00
psychobunny
6f6a2e4127 closes #114. updated ajaxify to allow for get parameters to be passed in without issues. 2013-07-23 15:06:31 +08:00
Julian Lam
9e7afcf0ab fixing issue where navigating to a non-existant category_id caused NodeBB
to crash
2013-07-22 20:29:51 -04:00
Julian Lam
741a5843a1 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-22 20:08:28 -04:00
Julian Lam
84584a1b5b minor cleanup of header tpl 2013-07-22 20:08:18 -04:00
Baris Soner Usakli
96a779172b closes #106 2013-07-22 18:29:09 -04:00
Baris Soner Usakli
2237166e0f removed console.log 2013-07-22 17:59:49 -04:00
Baris Soner Usakli
85b0fe7175 images debug 2013-07-22 17:26:18 -04:00
Baris Soner Usakli
e9afd4a107 fixed broken view again 2013-07-22 17:08:07 -04:00
Baris Soner Usakli
667a102c8b added async.eachSeries to getCategories 2013-07-22 16:57:18 -04:00
Baris Soner Usakli
38aa5aa3e6 refactor to remove category_name and category_slug from topic hashes 2013-07-22 16:47:41 -04:00
psychobunny
676ffee459 removed text decoration from btn-link to match that of default nodebb anchors 2013-07-23 03:51:32 +08:00
Julian Lam
87baaacb8c Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-22 15:46:02 -04:00
Julian Lam
7a1e1b5b60 fixing issue where notifications toggle was causing an ajaxify 2013-07-22 15:45:40 -04:00
psychobunny
1e86f379d5 removed external link icon from signatures only 2013-07-23 03:34:45 +08:00
Julian Lam
93d4630433 Merge remote-tracking branch 'origin' 2013-07-22 15:31:31 -04:00
Julian Lam
123aac0862 limiting notification counts (issue #112) 2013-07-22 15:31:28 -04:00
psychobunny
240683ed24 merged conflicts. also fixed a potential issue where the external link icon would show up on internal links 2013-07-23 03:15:17 +08:00
psychobunny
e66cab23cf added a screening page for external links. removed forced target = _blank on external links for UX consideration
used http://www.deviantart.com/users/outgoing?http://www.nodebb.org/ for
inspiration
2013-07-23 03:07:27 +08:00
Julian Lam
48e14e9464 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-22 15:05:33 -04:00
Julian Lam
712eec0872 re: issue #104, added external link icon to external links 2013-07-22 15:03:33 -04:00
Baris Usakli
f0df2289e6 i keep forgetting console.logs in commits :/ 2013-07-22 14:50:34 -04:00
Baris Usakli
22d954d01f fixed for broken category view, after topic move 2013-07-22 14:40:52 -04:00
Baris Usakli
d9fa78a866 again 2013-07-22 13:06:31 -04:00
Julian Lam
99ba792e6d changing all calls to marked to instead go through
PostTools.markdownToHTML
2013-07-22 12:58:10 -04:00
Julian Lam
f40bf46656 closed #104 - anchors now have rel="nofollow" and open in a new window 2013-07-22 12:44:50 -04:00
Julian Lam
104dbea3f2 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-22 12:11:46 -04:00
Baris Usakli
52110016c7 closes #107 2013-07-22 12:07:05 -04:00
Julian Lam
770fb6d33d Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-22 12:01:33 -04:00
Julian Lam
54c75024b5 css fixes to post window on mobile screens 2013-07-22 12:01:19 -04:00
Baris Usakli
676765278e closes #111 2013-07-22 11:16:16 -04:00
Julian Lam
667c331a67 refactored User.create in preparation for twitter compatibility (issue 2013-07-22 00:24:37 -04:00
Julian Lam
5e7da6391c fixed #100 - composer window obscuring half of the page 2013-07-21 23:47:11 -04:00
Julian Lam
9626232e3b some more tweaks to make dark themes work better 2013-07-21 23:34:22 -04:00
Julian Lam
81cfe0c8d0 Merge branch 'master' of github.com:designcreateplay/NodeBB
fixing missing height in post-window, removed for some reason

Conflicts:
	public/src/modules/composer.js
2013-07-21 23:27:07 -04:00
Baris Soner Usakli
e39868c08e fixed regex 2013-07-21 16:32:59 -04:00
Baris Soner Usakli
b25033be45 removed console.log 2013-07-21 16:17:39 -04:00
Baris Soner Usakli
7c31e12e6b removed debug console.logs 2013-07-21 16:16:59 -04:00
Baris Soner Usakli
863f471020 closes #103 2013-07-21 16:16:07 -04:00
Baris Soner Usakli
aa54ac8495 updated passport and passport-twitter 2013-07-21 15:07:11 -04:00
Baris Soner Usakli
0b7da34a03 possible fix for #101 2013-07-21 14:25:42 -04:00
Baris Soner Usakli
e437b4df66 removed commented out code 2013-07-21 13:58:41 -04:00
Baris Soner Usakli
919efd4052 drag and drop image upload first pass 2013-07-21 12:29:57 -04:00
Julian Lam
6f759e840e Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-21 02:34:21 -04:00
Julian Lam
a66f2dfabe minor tweaks to positioning of composer window relative to taskbar 2013-07-21 02:34:04 -04:00
Julian Lam
7619fec78d fixing compatibility with dark themes 2013-07-21 02:29:53 -04:00
Julian Lam
18c991fd42 restyling composer window to be smaller widthwise
attempted to fix vertical-alignment of post_window relative to taskbar
2013-07-21 02:16:58 -04:00
Julian Lam
a6460169d4 - removing underline from composer style shortcut button grouping 2013-07-21 01:43:14 -04:00
Baris Soner Usakli
f7291631d8 when a post is restored update topic timestamp 2013-07-20 16:59:53 -04:00
Baris Soner Usakli
eec5249eb6 possible fix for wront sorting on /recent 2013-07-20 16:01:44 -04:00
Baris Soner Usakli
93c6228347 handle err 2013-07-20 15:23:04 -04:00
Julian Lam
97a70b808e Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-20 11:19:24 -04:00
Julian Lam
782858c728 added dynamically generated robots.txt 2013-07-20 11:19:08 -04:00
Baris Soner Usakli
343aba9eff added relative path to image upload url 2013-07-20 00:10:45 -04:00
Julian Lam
0b545c7056 removing extra console log... 2013-07-19 23:58:30 -04:00
Julian Lam
3c4b2c8075 fixed oddities in base_url and url generation for subdirectoried instances
of NodeBB
2013-07-19 23:56:30 -04:00
Baris Usakli
e5301deea2 more async in categories 2013-07-19 16:13:00 -04:00
Baris Usakli
ff9990701b changed to async.eachSeries 2013-07-19 15:26:29 -04:00
Baris Usakli
c1b170ec86 removed anchor around recent posts on account page 2013-07-19 12:03:54 -04:00
Baris Usakli
78358a8ccd forgot ] 2013-07-19 11:02:21 -04:00
Baris Usakli
04bc0cd2c9 oops 2013-07-19 10:59:52 -04:00
Baris Usakli
a4de1b247f decrease/increase post count on post deletion/restore 2013-07-19 10:59:24 -04:00
Baris Usakli
3adddf44ca removed console.log 2013-07-19 10:37:18 -04:00
Baris Usakli
68c011ce63 recent replies link to user and topic 2013-07-19 10:36:42 -04:00
Baris Soner Usakli
2f20831d25 removed extra ; oops 2013-07-18 15:45:17 -04:00
Baris Soner Usakli
7ff6cd9cb2 closes #99 2013-07-18 15:44:42 -04:00
Baris Soner Usakli
285e19fbbf closes #97 2013-07-18 15:14:06 -04:00
Baris Soner Usakli
f90eb72706 added minimum lengths into error messages 2013-07-18 14:50:58 -04:00
Baris Soner Usakli
f7d27cdef1 nconf fixes 2013-07-18 14:47:41 -04:00
Baris Soner Usakli
4d670006e1 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-18 13:50:45 -04:00
Baris Soner Usakli
65029ae169 content length changes 2013-07-18 13:50:42 -04:00
Julian Lam
d0c4302d24 removing non-existant unit tests from admin template, and setting admin theme to always be the bootstrap default (so errant themes don't render the admin panel useless) 2013-07-18 12:04:29 -04:00
Julian Lam
800ac86014 committing missing file pertaining to issue #96 2013-07-17 22:38:55 -04:00
Julian Lam
1fa900e615 added sitemap.xml to routes, closes #96 2013-07-17 22:35:16 -04:00
Julian Lam
6e91810231 introduction of theme engine parsing (themes go in /public/themes!) 2013-07-17 16:17:19 -04:00
Julian Lam
936d29d907 Merge branch 'master' of github.com:psychobunny/node-forum 2013-07-17 16:14:23 -04:00
Julian Lam
44f9fe9f6c fixing issue where the email check always failed... whoops! 2013-07-17 16:13:53 -04:00
Baris Usakli
ca81732661 show correct version on admin/index 2013-07-17 16:09:01 -04:00
Baris Usakli
03d3f59804 change to async.each for addUserInfoToPosts 2013-07-17 15:48:09 -04:00
Baris Usakli
8369e2278f Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-17 12:58:02 -04:00
Baris Usakli
2bb4aab6ac changes to online users code 2013-07-17 12:57:57 -04:00
Julian Lam
a06123579b issue #94 2013-07-17 12:21:40 -04:00
Julian Lam
1e4424d656 issue #94 again 2013-07-17 12:17:52 -04:00
Julian Lam
d3541da9dd resolving regression introduced by errant install script (again) - issue 94 2013-07-17 12:15:40 -04:00
Julian Lam
2f1ef4c2a1 fixing regression introduced where the client-side config was incorrectly generated (resolves #94) 2013-07-17 12:05:24 -04:00
Julian Lam
f63be472be updated link to roadmap and added new screenshots for readme 2013-07-17 11:33:37 -04:00
Baris Usakli
4f9a2d0b93 content check in posts 2013-07-17 11:22:09 -04:00
Baris Usakli
8d21d4f998 closes #91 2013-07-17 10:45:16 -04:00
Baris Usakli
582cd2f011 closes #89 2013-07-17 10:05:29 -04:00
Julian Lam
ed967a0e2f applied minor suggestion suggested by @damianb in issue #24
closed #88 (regression due to text selection enhancement)
2013-07-17 01:02:28 -04:00
Julian Lam
3feb977cc4 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-16 21:13:02 -04:00
Julian Lam
c2e9cd621d issue #57, just for Damian :) 2013-07-16 21:12:40 -04:00
Baris Soner Usakli
9e5ecf0ae8 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-16 20:30:54 -04:00
Baris Soner Usakli
cf5f9ad9bd share links wont refresh page 2013-07-16 20:30:46 -04:00
Julian Lam
7cdef19b94 issue #57 again - tightened the regex a bit more (escaped the dot) 2013-07-16 16:38:35 -04:00
Julian Lam
0553254a9b closed #57 2013-07-16 16:37:37 -04:00
Julian Lam
8234294be8 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-07-16 16:37:13 -04:00
Baris Soner Usakli
62919894b4 removed the bcrypt question from install, changed default to 12 2013-07-16 16:33:18 -04:00
Baris Soner Usakli
dd4ee28d26 removed ajaxify.enable, closes #85 2013-07-16 16:21:12 -04:00
Baris Soner Usakli
a5b324cbc4 closes #86 2013-07-16 16:19:50 -04:00
59 changed files with 1839 additions and 820 deletions

1
.gitignore vendored
View File

@@ -8,5 +8,6 @@ sftp-config.json
config.json
public/config.json
public/css/*.css
public/themes/*
*.sublime-project
*.sublime-workspace

View File

@@ -4,14 +4,14 @@ Please support NodeBB development! Check out our IndieGoGo campaign and like, sh
# 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 Screenshot](http://i.imgur.com/mxRmLAg.png)
![NodeBB Main Category Listing](http://i.imgur.com/u2zamT4.png)
![NodeBB Login Page (with Social Logins)](http://i.imgur.com/q5tUUHW.png)
![NodeBB Topic Page](http://i.imgur.com/KrvSoXV.png)
## 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 [NodeBB Development/Roadmap Trello](https://trello.com/board/nodebb-development-roadmap/51c66b0d7c42d0a257002732)
* 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 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.

8
app.js
View File

@@ -35,7 +35,7 @@ console.log('Info: This is free software, and you are welcome to redistribute it
console.log('Info: ===');
if (!nconf.get('setup') && nconf.get('base_url')) {
nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + '/');
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/');
global.nconf = nconf;
@@ -70,16 +70,14 @@ if (!nconf.get('setup') && nconf.get('base_url')) {
config['ROOT_DIRECTORY'] = __dirname;
templates.init([
'header', 'footer', 'logout', '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/header', 'emails/footer', 'install/header', 'install/footer', 'install/redis',
'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic'
]);
templates.ready(function() {
webserver.init();
});
templates.ready(webserver.init);
//setup scripts to be moved outside of the app in future.
function setup_categories() {

View File

@@ -1,8 +1,8 @@
{
"name": "NodeBB",
"name": "nodebb",
"license": "GPLv3 or later",
"description": "NodeBB Forum",
"version": "0.0.3",
"version": "0.0.4",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -14,13 +14,12 @@
"redis": "0.8.3",
"express": "3.2.0",
"express-namespace": "0.1.1",
"connect": "2.7.6",
"emailjs": "0.3.4",
"cookie": "0.0.6",
"connect-redis": "1.4.5",
"passport": "0.1.16",
"passport": "0.1.17",
"passport-local": "0.1.6",
"passport-twitter": "0.1.4",
"passport-twitter": "0.1.5",
"passport-google-oauth": "0.1.5",
"passport-facebook": "0.1.5",
"less-middleware": "0.1.12",
@@ -30,7 +29,10 @@
"node-imagemagick": "0.1.8",
"node-rss": "1.0.1",
"gravatar": "1.0.6",
"nconf": "~0.6.7"
"nconf": "~0.6.7",
"sitemap": "~0.6.0",
"cheerio": "~0.12.0",
"request": "~2.25.0"
},
"bugs": {
"url": "https://github.com/designcreateplay/NodeBB/issues"

View File

@@ -20,11 +20,6 @@
&:last-child li {
border-bottom: 0;
}
color: #333;
&:hover {
color: #333
}
}
ul {
@@ -55,6 +50,7 @@
.topic-row {
border-radius: 5px;
padding-left: 20px;
border: 1px solid rgba(0, 0, 0, 0.2);
small {
vertical-align: 2px;
@@ -150,7 +146,8 @@
line-height: 16px;
margin-left: 1px;
padding: 5px 5px 5px 0px;
list-style-type: none;
li {
clear: both;
line-height: 16px;
@@ -167,6 +164,7 @@
p {
display: block;
padding-left:5px;
padding-top: 2px;
overflow: hidden;
height: 32px;
margin-bottom: 0.5em;
@@ -208,7 +206,6 @@
}
border-radius: 5px;
background: white;
padding: 0;
margin-bottom: 20px;

View File

@@ -201,6 +201,15 @@ footer.footer {
display:inline-block;
vertical-align:top;
}
.account-online-status {
.icon-circle-blank {
color:red;
}
.icon-circle {
color:green;
}
}
.user-profile-picture {
width:128px;
@@ -225,6 +234,10 @@ footer.footer {
div {
color: #333;
margin-bottom: 10px;
cursor: pointer;
p {
color: #333;
}
}
span {
padding-top: 10px;
@@ -346,7 +359,7 @@ footer.footer {
}
}
a:hover {
a:hover, .btn-link:hover, .btn-link:active, .btn-link:focus {
text-decoration:none;
}
@@ -399,10 +412,12 @@ body .navbar .nodebb-inline-block {
.icon-circle {
font-size: 12px;
color: green;
margin-right:3px;
}
.icon-circle-blank {
font-size: 12px;
color: red;
margin-right:3px;
}
}
@@ -497,13 +512,15 @@ body .navbar .nodebb-inline-block {
.post-window {
position: fixed;
bottom: 45px;
height: 450px;
display: none;
height: 350px;
visibility: hidden;
> div {
position: absolute;
height: 100%;
background: rgba(64, 64, 64, 0.6);
visibility: visible;
.btn-toolbar {
width: 90%;
@@ -511,18 +528,22 @@ body .navbar .nodebb-inline-block {
span {
color: white;
&:hover {
text-decoration: none;
}
}
}
input {
width: 100%;
width: 98%;
text-align: center;
background: rgba(255, 255, 255, 0.9);
border: none;
padding: 0.5em 0;
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
border-radius: 0px;
margin: 1% 1% 2% 1%;
}
textarea {
@@ -531,17 +552,54 @@ body .navbar .nodebb-inline-block {
padding: 0.5em;
display: block;
width: 90%;
margin: 1em auto;
margin: 0.5em auto;
resize: none;
color: white;
height: 330px;
height: 200px;
}
#imagedrop {
text-align:center;
color:white;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height:230px;
line-height:230px;
font-size:20px;
vertical-align: middle;
}
#imagelist {
position: absolute;
bottom: 5px;
left: 0px;
padding-left:2em;
div {
margin-right:5px;
}
span {
line-height:20px;
float:left;
}
button {
padding-left:5px;
}
}
}
}
@media (max-width: 979px) {
.post-window {
position: static;
position: relative;
bottom: 0px !important;
> div {
position: static;
width: 100% !important;
}
}
}

View File

@@ -6,10 +6,13 @@
clear: both;
.profile-image-block {
background: white;
display: inline-block;
text-align: center;
font-size: 12px;
.stats {
clear: both;
}
}
li {
@@ -22,14 +25,17 @@
}
.profile-block, .post-block {
border: 1px solid #f0f0f0;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 5px;
padding: 10px;
p {
line-height: 1.75em;
}
}
.profile-block {
background: #fafafa;
background: rgba(0, 0, 0, 0.02);
margin-right: -11px;
margin-left: -11px;
margin-bottom: -11px;
@@ -43,6 +49,9 @@
img.hidden-desktop {
max-width: 10px;
max-height: 10px;
padding-top: 5px;
margin-right: 5px;
}
}
.post-content {
@@ -51,18 +60,21 @@
word-wrap: break-word;
}
.post-images{
padding: 2px 5px 0 5px;
}
.post-block {
.post-buttons {
font-size: 12px;
float: right;
margin-right: 5px;
button {
button, a {
display: inline-block;
padding-left: 15px;
padding-right: 15px;
padding: 0px 15px;
border: none;
border-left: 1px solid #f0f0f0;
border-left: 1px solid rgba(0, 0, 0, 0.06);
cursor: pointer;
background: none;
font-size: 12px;
@@ -77,7 +89,6 @@
//theme this to make it yellow eventually
}
}
background: #fff;
}
&.deleted {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -28,6 +28,7 @@ var ajaxify = {};
ajaxify.go = function(url, callback, template, quiet) {
// leave room and join global
app.enter_room('global');
app.showLoginMessage();
var url = url.replace(/\/$/, "");
@@ -35,13 +36,13 @@ var ajaxify = {};
url = url.slice(RELATIVE_PATH.length);
}
var tpl_url = templates.get_custom_map(url);
var tpl_url = templates.get_custom_map(url.split('?')[0]);
if (tpl_url == false && !templates[url]) {
if(url === '' || url === '/') {
tpl_url = 'home';
} else {
tpl_url = url.split('/')[0];
tpl_url = url.split('/')[0].split('?')[0];
}
} else if (templates[url]) {
@@ -75,10 +76,6 @@ var ajaxify = {};
return false;
}
ajaxify.onclick = function(ev) {
}
$('document').ready(function() {
if (!window.history || !window.history.pushState) return; // no ajaxification for old browsers

View File

@@ -21,8 +21,9 @@ var socket,
socket.on('event:connect', function(data) {
console.log('connected to nodebb socket: ', data);
app.username = data.username;
});
socket.on('event:alert', function(data) {
app.alert(data);
});
@@ -43,6 +44,7 @@ var socket,
});
}, 1000);
reconnecting = false;
reconnectTries = 0;
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
}
});
@@ -86,9 +88,11 @@ var socket,
uid = el.parents('li').attr('data-uid');
if (uid && jQuery.inArray(uid, users) !== -1) {
el.prepend('<i class="icon-circle"></i>&nbsp;');
el.find('i').remove();
el.prepend('<i class="icon-circle"></i>');
} else {
el.prepend('<i class="icon-circle-blank"></i>&nbsp;');
el.find('i').remove();
el.prepend('<i class="icon-circle-blank"></i>');
}
el.processed = true;
@@ -208,25 +212,41 @@ var socket,
}
};
app.populate_online_users = function() {
var uids = [];
jQuery('.post-row').each(function() {
uids.push(this.getAttribute('data-uid'));
});
socket.emit('api:user.get_online_users', uids);
}
app.process_page = function() {
function populate_online_users() {
var uids = [];
jQuery('.post-row').each(function() {
uids.push(this.getAttribute('data-uid'));
});
socket.emit('api:user.get_online_users', uids);
}
// here is where all modules' onNavigate should be called, I think.
require(['mobileMenu'], function(mobileMenu) {
mobileMenu.onNavigate();
});
app.populate_online_users();
populate_online_users();
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length-1];
jQuery('#main-nav li').removeClass('active');
if(active) {
jQuery('#main-nav li a').each(function() {
var href = this.getAttribute('href');
if(active.match(/^users/))
active = 'users';
if (href && href.match(active)) {
jQuery(this.parentNode).addClass('active');
return false;
}
});
}
setTimeout(function() {
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
@@ -234,25 +254,18 @@ var socket,
}
app.showLoginMessage = function() {
if(location.href.indexOf('loggedin') !== -1) {
app.alert({
type: 'success',
title: 'Welcome Back ' + app.username + '!',
message: 'You have successfully logged in!',
timeout: 5000
});
}
}
jQuery('document').ready(function() {
// On menu click, change "active" state
var menuEl = document.querySelector('.nav'),
liEls = menuEl.querySelectorAll('li'),
logoutEl = document.getElementById('logout'),
parentEl;
menuEl.addEventListener('click', function(e) {
parentEl = e.target.parentNode;
if (parentEl.nodeName === 'LI') {
for(var x=0,numLis=liEls.length;x<numLis;x++) {
if (liEls[x] !== parentEl) liEls[x].className = '';
else parentEl.className = 'active';
}
}
}, false);
addTouchEvents();
});

View File

@@ -11,11 +11,11 @@
var postcount = $('#postcount');
postcount.html(app.addCommas(postcount.html()));
var editLink = $('#editLink');
var followBtn = $('#follow-btn');
if( yourid !== theirid) {
editLink.hide();
if(yourid === "0") {
followBtn.hide();
}
else if(yourid !== theirid) {
if(isFollowing)
followBtn.hide();
else
@@ -32,6 +32,24 @@
return false;
});
$('.user-recent-posts .topic-row').on('click', function() {
ajaxify.go($(this).attr('topic-url'));
});
var onlineStatus = $('.account-online-status');
socket.on('api:user.isOnline', function(online) {
if(online) {
onlineStatus.find('span span').text('online');
onlineStatus.find('i').attr('class', 'icon-circle');
} else {
onlineStatus.find('span span').text('offline');
onlineStatus.find('i').attr('class', 'icon-circle-blank');
}
});
socket.emit('api:user.isOnline', theirid);
});
}());

View File

@@ -0,0 +1,20 @@
(function() {
var yourid = templates.get('yourid'),
theirid = templates.get('theirid');
$(document).ready(function() {
var editLink = $('#editLink');
var settingsLink = $('#settingsLink');
if(yourid === "0") {
editLink.hide();
settingsLink.hide();
}
else if(yourid !== theirid) {
editLink.hide();
settingsLink.hide();
}
});
}());

View File

@@ -0,0 +1,16 @@
$(document).ready(function() {
$('#submitBtn').on('click', function() {
var settings = {
showemail: $('#showemailCheckBox').is(':checked')?1:0
};
socket.emit('api:user.saveSettings', settings);
return false;
});
});

View File

@@ -7,7 +7,7 @@ var nodebb_admin = (function(nodebb_admin) {
themes.render = function(bootswatch) {
var themeFrag = document.createDocumentFragment(),
themeEl = document.createElement('li'),
themeContainer = document.querySelector('#content .themes'),
themeContainer = document.querySelector('#bootstrap_themes'),
numThemes = bootswatch.themes.length;
for(var x=0;x<numThemes;x++) {
@@ -42,30 +42,33 @@ var nodebb_admin = (function(nodebb_admin) {
scriptEl.src = 'http://api.bootswatch.com?callback=nodebb_admin.themes.render';
document.body.appendChild(scriptEl);
var themeContainer = document.querySelector('#content .themes');
themeContainer.addEventListener('click', function(e) {
if (e.target.hasAttribute('data-action')) {
switch(e.target.getAttribute('data-action')) {
case 'preview':
var cssSrc = $(e.target).parents('li').attr('data-css'),
cssEl = document.getElementById('base-theme');
var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
installedThemeContainer = document.querySelector('#installed_themes'),
themeEvent = function(e) {
if (e.target.hasAttribute('data-action')) {
switch(e.target.getAttribute('data-action')) {
case 'preview':
var cssSrc = $(e.target).parents('li').attr('data-css'),
cssEl = document.getElementById('base-theme');
cssEl.href = cssSrc;
break;
case 'use':
var parentEl = $(e.target).parents('li'),
cssSrc = parentEl.attr('data-css'),
cssName = parentEl.attr('data-theme');
socket.emit('api:config.set', {
key: 'theme:id', value: 'bootswatch:' + cssName
});
socket.emit('api:config.set', {
key: 'theme:src', value: cssSrc
});
break;
cssEl.href = cssSrc;
break;
case 'use':
var parentEl = $(e.target).parents('li'),
cssSrc = parentEl.attr('data-css'),
cssName = parentEl.attr('data-theme');
socket.emit('api:config.set', {
key: 'theme:id', value: 'bootswatch:' + cssName
});
socket.emit('api:config.set', {
key: 'theme:src', value: cssSrc
});
break;
}
}
}
}, false);
};
bootstrapThemeContainer.addEventListener('click', themeEvent);
installedThemeContainer.addEventListener('click', themeEvent);
var revertEl = document.getElementById('revert_theme');
revertEl.addEventListener('click', function() {
@@ -76,4 +79,34 @@ var nodebb_admin = (function(nodebb_admin) {
}
});
}, false);
// Installed Themes
socket.once('api:admin:themes.getInstalled', function(themes) {
var instListEl = document.getElementById('installed_themes'),
themeFrag = document.createDocumentFragment(),
liEl = document.createElement('li');
for(var x=0,numThemes=themes.length;x<numThemes;x++) {
liEl.setAttribute('data-theme', themes[x].id);
liEl.setAttribute('data-css', themes[x].src);
liEl.innerHTML = '<img src="' + themes[x].screenshot + '" />' +
'<div>' +
'<div class="pull-right">' +
'<button class="btn btn-primary" data-action="use">Use</button> ' +
'<button class="btn" data-action="preview">Preview</button>' +
'</div>' +
'<h4>' + themes[x].name + '</h4>' +
'<p>' +
themes[x].description +
(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
'</p>' +
'</div>' +
'<div class="clear">';
themeFrag.appendChild(liEl.cloneNode(true));
}
instListEl.innerHTML = '';
instListEl.appendChild(themeFrag);
});
socket.emit('api:admin:themes.getInstalled');
})();

View File

@@ -12,12 +12,15 @@
twitterEl.addEventListener('click', function() {
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
return false;
}, false);
facebookEl.addEventListener('click', function() {
window.open(facebook_url, '_blank', 'width=626,height=436,scrollbars=no,status=no');
return false;
}, false);
googleEl.addEventListener('click', function() {
window.open(google_url, '_blank', 'width=500,height=570,scrollbars=no,status=no');
return false;
}, false);
var new_post = document.getElementById('new_post');
@@ -56,8 +59,6 @@
container.insertBefore(topic, null);
$(topic).hide().fadeIn('slow');
}
ajaxify.enable();
});
@@ -76,11 +77,15 @@
for (var i=0,numPosts=posts.length; i<numPosts; i++) {
var dateString = utils.relativeTime(posts[i].timestamp);
li.setAttribute('data-pid', posts[i].pid);
li.innerHTML = '<img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-polaroid" src="' + posts[i].picture + '" class="" />' +
'<p>' +
'<strong>' + posts[i].username + '</strong>: ' + posts[i].content +
'</p>' +
'<span>posted ' + utils.relativeTime(posts[i].timestamp) + ' ago</span>';
li.innerHTML = '<a href="/users/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-polaroid" src="' + posts[i].picture + '" class="" /></a>' +
'<a href="/topic/'+ posts[i].tid + '">' +
'<p>' +
'<strong>' + posts[i].username + '</strong>: ' + posts[i].content +
'</p>' +
'<span>posted ' + utils.relativeTime(posts[i].timestamp) + ' ago</span>' +
'</a>';
frag.appendChild(li.cloneNode(true));
recent_replies.appendChild(frag);

View File

@@ -9,12 +9,7 @@
if(parseInt(followersCount, 10) === 0) {
$('#no-followers-notice').show();
}
var editLink = $('#editLink');
if(yourid !== theirid) {
editLink.hide();
}
$('.reputation').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});

View File

@@ -9,10 +9,8 @@
if(parseInt(followingCount, 10) === 0) {
$('#no-following-notice').show();
}
var editLink = $('#editLink');
if(yourid !== theirid) {
editLink.hide();
$('.unfollow-btn').hide();
}
else {

View File

@@ -41,9 +41,9 @@
});
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
socket.on('api:updateHeader', function(data) {
var rightMenu = $('#right-menu');
if (data.uid > 0) {
@@ -52,14 +52,14 @@
if(data['userslug'])
userLabel.attr('href','/users/' + data['userslug']);
if(data['picture'])
userLabel.find('img').attr('src',data['picture']+"?s=24&default=identicon");
userLabel.find('img').attr('src',data['picture']);
if(data['username'])
userLabel.find('span').html(data['username']);
}
else {
var userli = $('<li> \
<a id="user_label" href="/users/'+data['userslug']+'"> \
<img src="'+data['picture']+"?s=24&default=identicon"+'"/> \
<img src="'+data['picture']+'"/> \
<span>'+data['username']+'</span> \
</a> \
</li>');
@@ -86,7 +86,8 @@
var notifContainer = document.getElementsByClassName('notifications')[0],
notifTrigger = notifContainer.querySelector('a'),
notifList = document.getElementById('notif-list');
notifTrigger.addEventListener('click', function() {
notifTrigger.addEventListener('click', function(e) {
e.preventDefault();
if (notifContainer.className.indexOf('open') === -1) socket.emit('api:notifications.get');
});
notifList.addEventListener('click', function(e) {
@@ -145,7 +146,6 @@
socket.on('chatMessage', function(data) {
var username = data.username;
var fromuid = data.fromuid;
var message = data.message;

View File

@@ -28,7 +28,7 @@
data: loginData,
success: function(data, textStatus, jqXHR) {
$('#login-error-notify').hide();
window.location.replace(RELATIVE_PATH + "/");
window.location.replace(RELATIVE_PATH + "/?loggedin");
},
error : function(data, textStatus, jqXHR) {
$('#login-error-notify').show().delay(1000).fadeOut(250);

View File

@@ -232,7 +232,7 @@
if (thread_state.locked !== '1') {
require(['composer'], function(cmp) {
cmp.push(tid, null, null, selectionText + '\n\n');
cmp.push(tid, null, null, selectionText.length > 0 ? selectionText + '\n\n' : '');
});
}
};
@@ -259,16 +259,14 @@
pid = ids[0],
uid = ids[1];
if (thread_state.locked !== '1') {
var element = $(this).find('i');
if(element.attr('class') == 'icon-star-empty') {
element.attr('class', 'icon-star');
socket.emit('api:posts.favourite', {pid: pid, room_id: app.current_room});
}
else {
element.attr('class', 'icon-star-empty');
socket.emit('api:posts.unfavourite', {pid: pid, room_id: app.current_room});
}
var element = $(this).find('i');
if(element.attr('class') == 'icon-star-empty') {
element.attr('class', 'icon-star');
socket.emit('api:posts.favourite', {pid: pid, room_id: app.current_room});
}
else {
element.attr('class', 'icon-star-empty');
socket.emit('api:posts.unfavourite', {pid: pid, room_id: app.current_room});
}
});
@@ -276,10 +274,7 @@
var pid = ($(this).attr('id') || $(this.parentNode).attr('id')).split('_')[1];
var main = $(this).parents('.main-post');
// if(main.length > 0)
// app.open_post_window('edit', tid, topic_name, pid);
// else
// app.open_post_window('edit', tid, "", pid);
require(['composer'], function(cmp) {
cmp.push(null, null, pid);
});
@@ -316,7 +311,7 @@
'event:topic_deleted', 'event:topic_restored', 'event:topic:locked',
'event:topic_unlocked', 'event:topic_pinned', 'event:topic_unpinned',
'event:topic_moved', 'event:post_edited', 'event:post_deleted', 'event:post_restored',
'api:posts.favourite', 'chatMessage'
'api:posts.favourite'
]);
@@ -324,6 +319,8 @@
var activeEl = $('#thread_active_users');
if(activeEl.length)
activeEl.html(data);
app.populate_online_users();
});
socket.on('event:rep_up', function(data) {
@@ -351,6 +348,7 @@
tempContainer.replaceWith(tempContainer.contents());
infiniteLoaderActive = false;
app.populate_online_users();
addCommasToNumbers();
});

View File

@@ -35,7 +35,7 @@ define(['taskbar'], function(taskbar) {
taskbar.discard('chat', uuid);
});
chatModal.on('click', function(e){
chatModal.on('click', function(e) {
module.bringModalToTop(chatModal);
});

View File

@@ -1,20 +1,92 @@
define(['taskbar'], function(taskbar) {
var composer = {
initialized: false,
active: 0,
active: undefined,
taskbar: taskbar,
posts: {},
postContainer: undefined,
};
function loadFile(file) {
var reader = new FileReader();
var dropDiv = $('#imagedrop');
var imagelist = $('#imagelist');
var uuid = dropDiv.parents('[data-uuid]').attr('data-uuid');
var posts = composer.posts[uuid];
$(reader).on('loadend', function(e) {
var bin = this.result;
bin = bin.split(',')[1];
var img = {
name: file.name,
data: bin
};
posts.images.push(img);
var imageLabel = $('<div class="label"><span>'+ file.name +'</span></div>');
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);
dropDiv.hide();
});
reader.readAsDataURL(file);
}
function initializeFileReader() {
jQuery.event.props.push( "dataTransfer" );
if(window.FileReader) {
var drop = $('#imagedrop');
$(composer.postContainer).on('dragenter dragover', function() {
drop.show();
});
function cancel(e) {
e.preventDefault();
return false;
}
drop.on('dragover', cancel);
drop.on('dragenter', cancel);
drop.on('drop', function(e) {
e.preventDefault();
var uuid = drop.parents('[data-uuid]').attr('data-uuid');
var posts = composer.posts[uuid];
var dt = e.dataTransfer;
var files = dt.files;
for (var i=0; i<files.length; i++) {
loadFile(files[i]);
}
return false;
});
}
}
composer.init = function() {
if (!composer.initialized) {
// Create the fixed bottom bar
var taskbar = document.getElementById('taskbar');
var taskbar = document.getElementById('taskbar');
composer.postContainer = document.createElement('div');
composer.postContainer.className = 'post-window row-fluid';
composer.postContainer.innerHTML = '<div class="span10 offset1">' +
composer.postContainer.innerHTML = '<div class="span5">' +
'<input type="text" tabIndex="1" placeholder="Enter your topic title here..." />' +
'<div class="btn-toolbar">' +
'<div class="btn-group formatting-bar">' +
@@ -23,20 +95,28 @@ define(['taskbar'], function(taskbar) {
'<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>' +
'</div>' +
'<div class="btn-group action-bar" style="float: right; margin-right: -12px">' +
'<button data-action="post" class="btn" tabIndex="3"><i class="icon-ok"></i> Submit</button>' +
'</div>' +
'<div style="position:relative;">'+
'<div id="imagedrop" class=""><div>Drag and Drop Images Here</div></div>'+
'<textarea tabIndex="2"></textarea>' +
'<div id="imagelist"></div>'+
'</div>'+
'<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 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>' +
'</div>' +
'</div>' +
'<textarea tabIndex="2"></textarea>' +
'</div>';
document.body.insertBefore(composer.postContainer, taskbar);
initializeFileReader();
socket.on('api:composer.push', function(threadData) {
if (!threadData.error) {
var uuid = utils.generateUUID();
var uuid = utils.generateUUID();
composer.taskbar.push('composer', uuid, {
title: (!threadData.cid ? (threadData.title || '') : 'New Topic'),
@@ -48,7 +128,8 @@ define(['taskbar'], function(taskbar) {
cid: threadData.cid,
pid: threadData.pid,
title: threadData.title || '',
body: threadData.body || ''
body: threadData.body || '',
images: []
};
composer.load(uuid);
} else {
@@ -141,6 +222,9 @@ define(['taskbar'], function(taskbar) {
break;
}
});
window.addEventListener('resize', function() {
if (composer.active !== undefined) composer.reposition(composer.active);
});
composer.initialized = true;
}
@@ -156,12 +240,18 @@ define(['taskbar'], function(taskbar) {
}
composer.load = function(post_uuid) {
var post_data = composer.posts[post_uuid],
var post_data = composer.posts[post_uuid],
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');
composer.postContainer.style.display = 'block';
// composer.postContainer.style.bottom = composer.btnContainer.offsetHeight + "px";
dropDiv.hide();
imagelist.empty();
composer.reposition(post_uuid);
composer.active = post_uuid;
composer.postContainer.setAttribute('data-uuid', post_uuid);
if (parseInt(post_data.tid) > 0) {
titleEl.value = 'Replying to: ' + post_data.title;
@@ -186,17 +276,45 @@ define(['taskbar'], function(taskbar) {
}
}
composer.reposition = function(post_uuid) {
var postWindowEl = composer.postContainer.querySelector('.span5'),
taskbarBtn = document.querySelector('#taskbar [data-uuid="' + post_uuid + '"]'),
btnRect = taskbarBtn.getBoundingClientRect(),
taskbarRect = document.getElementById('taskbar').getBoundingClientRect(),
windowRect, leftPos;
composer.postContainer.style.display = 'block';
windowRect = postWindowEl.getBoundingClientRect();
leftPos = btnRect.left + btnRect.width - windowRect.width;
postWindowEl.style.left = (leftPos > 0 ? leftPos : 0) + 'px';
composer.postContainer.style.bottom = taskbarRect.height + "px";
}
composer.post = function(post_uuid) {
// Check title and post length
var postData = composer.posts[post_uuid],
titleEl = composer.postContainer.querySelector('input'),
bodyEl = composer.postContainer.querySelector('textarea');
if (titleEl.value.length <= 3) {
titleEl.value = titleEl.value.trim();
bodyEl.value = bodyEl.value.trim();
if (titleEl.value.length < 3) {
return app.alert({
type: 'error',
timeout: 5000,
timeout: 2000,
title: 'Title too short',
message: "Please enter a longer title.",
message: "Please enter a longer title. At least 3 characters.",
alert_id: 'post_error'
});
}
if (bodyEl.value.length < 8) {
return app.alert({
type: 'error',
timeout: 2000,
title: 'Content too short',
message: "Please enter a longer post. At least 8 characters.",
alert_id: 'post_error'
});
}
@@ -206,18 +324,21 @@ define(['taskbar'], function(taskbar) {
socket.emit('api:topics.post', {
'title' : titleEl.value,
'content' : bodyEl.value,
'category_id' : postData.cid
'category_id' : postData.cid,
images: composer.posts[post_uuid].images
});
} else if (parseInt(postData.tid) > 0) {
socket.emit('api:posts.reply', {
'topic_id' : postData.tid,
'content' : bodyEl.value
'content' : bodyEl.value,
images: composer.posts[post_uuid].images
});
} else if (parseInt(postData.pid) > 0) {
socket.emit('api:posts.edit', {
pid: postData.pid,
content: bodyEl.value,
title: titleEl.value
title: titleEl.value,
images: composer.posts[post_uuid].images
});
}
@@ -226,6 +347,7 @@ define(['taskbar'], function(taskbar) {
composer.discard = function(post_uuid) {
if (composer.posts[post_uuid]) {
$(composer.postContainer).find('#imagedrop').html('');
delete composer.posts[post_uuid];
composer.minimize();
taskbar.discard('composer', post_uuid);
@@ -234,6 +356,7 @@ define(['taskbar'], function(taskbar) {
composer.minimize = function(uuid) {
composer.postContainer.style.display = 'none';
composer.active = undefined;
taskbar.minimize('composer', uuid);
}

View File

@@ -106,7 +106,7 @@
}
templates.getTemplateNameFromUrl = function(url) {
var parts = url.split('/');
var parts = url.split('?')[0].split('/');
for(var i=0; i<parts.length; ++i) {
if (templates.is_available(parts[i])) {
@@ -123,7 +123,7 @@
var api_url = (url === '' || url === '/') ? 'home' : url;
var tpl_url = templates.get_custom_map(api_url);
var tpl_url = templates.get_custom_map(api_url.split('?')[0]);
var trimmed = api_url;

View File

@@ -87,7 +87,7 @@
isEmailValid: function(email) {
// var re = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
var valid = email.indexOf('@') !== -1 ? true : false;
return re.test(email);
return valid;
},
isUserNameValid: function(name) {
@@ -107,6 +107,23 @@
return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
});
},
buildMetaTags: function(tagsArr) {
var tags = '',
tag;
for(var x=0,numTags=tagsArr.length;x<numTags;x++) {
if (tags.length > 0) tags += "\n\t";
tag = '<meta';
for(y in tagsArr[x]) {
tag += ' ' + y + '="' + tagsArr[x][y] + '"';
}
tag += ' />';
tags += tag;
}
return tags;
}
}

View File

@@ -8,8 +8,9 @@
<a href="/users/{userslug}">{username}</a>
</span>
<div class="account-sub-links inline-block pull-right">
<span id="followersLink" class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span id="followingLink" class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<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>
@@ -20,6 +21,9 @@
<div class="account-picture-block">
<img src="{picture}" class="user-profile-picture img-polaroid"/>
</div>
<div class="account-online-status">
<span><i class="icon-circle-blank"></i> <span>offline</span></span>
</div>
<div id="user-actions">
<a id="follow-btn" href="#" class="btn">Follow</a>
</div>
@@ -29,7 +33,7 @@
<h4>profile</h4>
<div class="inline-block">
<div class="account-bio-block">
<span class="account-bio-label">email</span>
<span class="account-bio-label">email</span><i class="icon-eye-close {emailClass}" title="Email hidden"></i>
<span>{email}</span>
<br/>
@@ -81,13 +85,10 @@
<div class="span6 user-recent-posts">
<h4>recent posts </h4>
<!-- BEGIN posts -->
<a href="/topic/{posts.tid}/{posts.pid}">
<div class="topic-row img-polaroid clearfix">
<span>{posts.content}</span>
<span class="pull-right">{posts.relativeTime} ago</span>
</div>
</a>
<div class="topic-row img-polaroid clearfix" topic-url="topic/{posts.tid}/{posts.pid}">
<span>{posts.content}</span>
<span class="pull-right">{posts.relativeTime} ago</span>
</div>
<!-- END posts -->
</div>
</div>
@@ -101,4 +102,5 @@
<input type="hidden" template-variable="theirid" value="{theirid}" />
<input type="hidden" template-type="boolean" template-variable="isFollowing" value="{isFollowing}" />
<script type="text/javascript" src="{relative_path}/src/forum/account.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/account.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>

View File

@@ -40,9 +40,9 @@
</div>
<div class="modal-body">
<form id="uploadForm" action="/users/uploadpicture" method="post" enctype="multipart/form-data">
<input id="userPhotoInput" type="file" name="userPhoto" >
<input id="imageUploadCsrf" type="hidden" name="_csrf" value="">
<form id="uploadForm" action="{relative_path}/users/uploadpicture" method="post" enctype="multipart/form-data">
<input id="userPhotoInput" type="file" name="userPhoto" />
<input id="imageUploadCsrf" type="hidden" name="_csrf" value="" />
</form>
<div id="upload-progress-box" class="progress progress-striped active hide">
@@ -62,12 +62,13 @@
<div class="account-username-box">
<span class="account-username">
<a href="/users/{userslug}">{username}</a> >
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/edit">edit</a>
</span>
<div class="account-sub-links inline-block pull-right">
<span id="followersLink" class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span id="followingLink" class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<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>
@@ -170,4 +171,4 @@
<input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" />
<input type="hidden" template-variable="uploadedpicture" value="{uploadedpicture}" />
<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

@@ -0,0 +1,34 @@
<div class="well">
<div class="account-username-box">
<span class="account-username">
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/settings">settings</a>
</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 class="row-fluid">
<div class="span6">
<h4>privacy</h4>
<label class="checkbox">
<input id="showemailCheckBox" type="checkbox" {showemail}> Show my email
</label>
</div>
<div class="span6">
</div>
</div>
<div class="form-actions">
<a id="submitBtn" href="#" class="btn btn-primary">Save changes</a>
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/accountsettings.js"></script>

View File

@@ -6,7 +6,7 @@
<script>
var RELATIVE_PATH = "{relative_path}";
</script>
<link id="base-theme" href="{cssSrc}" rel="stylesheet" media="screen">
<link id="base-theme" href="{relative_path}/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="{relative_path}/vendor/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
@@ -87,14 +87,14 @@
<li class="nav-header">Unit Tests</li>
<ul class="nav nav-list">
<li class=''><a href='{relative_path}/admin/testing/categories'>Categories</a></li>
<li class=''><a href='{relative_path}/admin/testing/topics'>Topics</a></li>
<!--<li class=''><a href='{relative_path}/admin/testing/topics'>Topics</a></li>
<li class=''><a href='{relative_path}/admin/testing/posts'>Posts</a></li>
<li class=''><a href='{relative_path}/admin/testing/accounts'>Accounts</a></li>
<li class=''><a href='{relative_path}/admin/testing/chat'>Chat</a></li>
<li class=''><a href='{relative_path}/admin/testing/notifications'>Notifications</a></li>
<li class=''><a href='{relative_path}/admin/testing/friends'>Friends</a></li>
<li class=''><a href='{relative_path}/admin/testing/feed'>RSS Feed</a></li>
<li class=''><a href='{relative_path}/admin/testing/emails'>Emails</a></li>
<li class=''><a href='{relative_path}/admin/testing/emails'>Emails</a></li>-->
</ul>
</ul>
</div><!--/.well -->

View File

@@ -8,7 +8,7 @@
<a target="_blank" href="http://www.nodebb.org" class="btn btn-large"><i class="icon-github-alt"></i> Get Themes</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-large"><i class="icon-twitter"></i> dcplabs</a>
</p>
<p><small>You are running <strong>NodeBB v0.0.1</strong>. This is where we will check to make sure your <strong>NodeBB</strong> is latest, etc.</small></p>
<p><small>You are running <strong>NodeBB v{version}</strong>. This is where we will check to make sure your <strong>NodeBB</strong> is latest, etc.</small></p>
</div>

View File

@@ -5,7 +5,9 @@
<div class="alert">
<form>
<label>Site Title</label>
<input type="text" placeholder="My Forum" data-field="title" />
<input type="text" placeholder="Your Community Name" data-field="title" />
<label>Site Description</label>
<input type="text" class="input-xxlarge" placeholder="A short description about your community" data-field="description" />
</form>
</div>

View File

@@ -1,12 +1,20 @@
<h1>Themes</h1>
<hr />
<h3>Custom Themes</h3>
<p>
The following themes are currently installed in this NodeBB instance.
</p>
<ul class="themes" id="installed_themes">
<li><i class="icon-refresh icon-spin"></i> Checking for installed themes...</li>
</ul>
<h3>Bootswatch Themes</h3>
<p>
NodeBB Themes are powered by Bootswatch, a repository containing themes built
with Bootstrap as a base theme.
</p>
<ul class="themes">
<ul class="themes" id="bootstrap_themes">
<li><i class="icon-refresh icon-spin"></i> Loading Themes</li>
</ul>

View File

@@ -14,6 +14,21 @@
<strong>There are no topics in this category.</strong><br />
Why don't you try posting one?
</div>
<div>
<button id="new_post" class="btn btn-primary btn-large {show_topic_button}">New Topic</button>
<div class="inline-block pull-right">
<a target="_blank" href="../{category_id}.rss"><i class="icon-rss-sign icon-2x"></i></a>&nbsp;
<a href="#" id="facebook-share"><i class="icon-facebook-sign icon-2x"></i></a>&nbsp;
<a href="#" id="twitter-intent"><i class="icon-twitter-sign icon-2x"></i></a>&nbsp;
<a href="#" id="google-share"><i class="icon-google-plus-sign icon-2x"></i></a>&nbsp;
</div>
</div>
<hr class="{show_sidebar}" />
<div class="category row">
<div class="{topic_row_size}">
<ul id="topics-container">
@@ -26,7 +41,7 @@
38
</span>
</div> -->
<div class="span12 topic-row img-polaroid">
<div class="span12 topic-row">
<div class="latest-post visible-desktop">
<div class="pull-right">
<img style="width: 48px; height: 48px; /*temporary*/" src="{topics.teaser_userpicture}" />
@@ -47,18 +62,9 @@
</li></a>
<!-- END topics -->
</ul>
<hr class="{show_sidebar}" />
<button id="new_post" class="btn btn-primary btn-large {show_topic_button}">New Topic</button>
</div>
<div class="span3 {show_sidebar} category-sidebar mobile-sidebar">
<div class="sidebar-block img-polaroid">
<div class="block-header">
<a target="_blank" href="../{category_id}.rss"><i class="icon-rss-sign icon-2x"></i></a>&nbsp;
<a href="#" id="facebook-share"><i class="icon-facebook-sign icon-2x"></i></a>&nbsp;
<a href="#" id="twitter-intent"><i class="icon-twitter-sign icon-2x"></i></a>&nbsp;
<a href="#" id="google-share"><i class="icon-google-plus-sign icon-2x"></i></a>&nbsp;
</div>
</div>
<div class="sidebar-block img-polaroid">
<div class="block-header">
Recent Replies

View File

@@ -23,6 +23,7 @@
"users[^]*edit": "accountedit",
"users[^]*following": "following",
"users[^]*followers": "followers",
"users[^]*settings": "accountsettings",
"users/[^]*": "account",
"recent": "recent",

View File

@@ -3,12 +3,13 @@
<div class="account-username-box">
<span class="account-username">
<a href="/users/{userslug}">{username}</a> >
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/followers">followers</a>
</span>
<div class="account-sub-links inline-block pull-right">
<span id="followersLink" class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span id="followingLink" class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<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>
@@ -41,4 +42,5 @@
<input type="hidden" template-variable="theirid" value="{theirid}" />
<input type="hidden" template-variable="followersCount" value="{followersCount}" />
<script type="text/javascript" src="{relative_path}/src/forum/followers.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/followers.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>

View File

@@ -5,12 +5,13 @@
<div class="account-username-box">
<span class="account-username">
<a href="/users/{userslug}">{username}</a> >
<a href="/users/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/users/{userslug}/following">following</a>
</span>
<div class="account-sub-links inline-block pull-right">
<span id="followersLink" class="pull-right"><a href="/users/{userslug}/followers">followers</a></span>
<span id="followingLink" class="pull-right"><a href="/users/{userslug}/following">following</a></span>
<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>
@@ -46,3 +47,4 @@
<input type="hidden" template-variable="followingCount" value="{followingCount}" />
<script type="text/javascript" src="{relative_path}/src/forum/following.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>

View File

@@ -1,19 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" CONTENT="NodeBB">
<meta name="keywords" content="" />
<meta name="description" content="" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<script>
var RELATIVE_PATH = "{relative_path}";
</script>
<title>{browserTitle}</title>
{meta_tags}
<link href="{cssSrc}" rel="stylesheet" media="screen">
<link href="{relative_path}/vendor/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
<script>
var RELATIVE_PATH = "{relative_path}";
</script>
<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript" src="{relative_path}/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js"></script>
<script type="text/javascript" src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
@@ -50,7 +45,7 @@
<span class="icon-bar"></span>
</button>
<div class="nav-collapse collapse">
<ul class="nav nodebb-inline-block">
<ul id="main-nav" class="nav nodebb-inline-block">
<li>
<a href="/recent">Recent <!--<span class="badge badge-inverse">3</span>--></a>
</li>

View File

@@ -14,7 +14,7 @@
<!-- BEGIN posts -->
<div class="category-box">
<div class="post-preview">
<img src="{categories.posts.picture}" class="pull-left" >
<img src="{categories.posts.picture}" class="pull-left" />
<p class=""><strong>{categories.posts.username}</strong>: {categories.posts.content}</p>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<div class="hero-unit">
<h2 class="form-signin-heading">Now Leaving NodeBB</h2>
<p>
You are now leaving NodeBB.
</p>
<br />
<p>
<a href="{url}" rel="nofollow" class="btn btn-large">Continue to {url}</a>
<a href="{home}" class="btn btn-large btn-inverse">Return to NodeBB</a>
</p>
</div>

View File

@@ -29,7 +29,7 @@
<div class="hover-overlay">
{main_posts.username}<br />
<i class="icon-star"></i><span class="user_rep_{main_posts.uid}">{main_posts.user_rep}</span>
<i class="icon-pencil"></i><span class="user_posts_{main_posts.uid}">8</span>
<i class="icon-pencil"></i><span class="user_posts_{main_posts.uid}">{main_posts.user_postcount}</span>
</div>
</a>
<h3><p id="topic_title_{main_posts.pid}" class="topic-title">{topic_name}</p></h3>
@@ -38,28 +38,38 @@
<hr />
<div class="topic-buttons pull-left">
<a href="/users/{main_posts.userslug}" class="username-field btn hidden-phone">{main_posts.username}</a>
<a target="_blank" class="btn hidden-phone" href="../{topic_id}.rss" title="RSS Feed"><i class="icon-rss-sign"></i></a>
<button class="btn follow" type="button" title="Be notified of new replies in this topic"><i class="icon-eye-open"></i></button>
<button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn edit {main_posts.display_moderator_tools}" type="button" title="Edit"><i class="icon-pencil"></i></button>
<button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn delete {main_posts.display_moderator_tools}" type="button" title="Delete"><i class="icon-trash"></i></button>
<button id="quote_{main_posts.pid}_{main_posts.uid}" class="btn quote" type="button" title="Quote"><i class="icon-quote-left"></i></button>
<button id="favs_{main_posts.pid}_{main_posts.uid}" class="favourite btn" type="button">
<span>Favourite</span>
<span class="post_rep_{main_posts.pid}">{main_posts.post_rep} </span><i class="{main_posts.fav_star_class}"></i></button>
<button class="btn btn-primary btn post_reply hidden-phone" type="button">Reply <i class="icon-reply"></i></button>
<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 id="favs_{main_posts.pid}_{main_posts.uid}" class="favourite btn" type="button">
<span>Favourite</span>
<span class="post_rep_{main_posts.pid}">{main_posts.post_rep} </span><i class="{main_posts.fav_star_class}"></i>
</button>
</div>
<div class="btn-group">
<button id="quote_{main_posts.pid}_{main_posts.uid}" class="btn quote" type="button" title="Quote"><i class="icon-quote-left"></i></button>
<button class="btn btn-primary btn post_reply" type="button">Reply <i class="icon-reply"></i></button>
</div>
</div>
<div style="clear:both; margin-bottom: 10px;"></div>
<div id="content_{main_posts.pid}" class="post-content">{main_posts.content}</div>
<div id="images_{main_posts.pid}" class="post-images">
<!-- BEGIN uploadedImages -->
<i class="icon-picture icon-1"></i><a href="{main_posts.uploadedImages.url}"> {main_posts.uploadedImages.name}</a><br/>
<!-- END uploadedImages -->
</div>
<div class="post-signature">{main_posts.signature}</div>
<div class="profile-block">
<img class="hidden-desktop" src="{main_posts.picture}?s=10&default=identicon" align="left" /> posted by <strong><a class="" href="/users/{main_posts.userslug}">{main_posts.username}</a></strong> {main_posts.relativeTime} ago
<img class="hidden-desktop" src="{main_posts.picture}" align="left" /> posted by <strong><a class="username-field" href="/users/{main_posts.userslug}">{main_posts.username}</a></strong> {main_posts.relativeTime} ago
<span class="{main_posts.edited-class} hidden-phone">| last edited by <strong><a href="/users/{main_posts.editorslug}">{main_posts.editorname}</a></strong> {main_posts.relativeEditTime} ago</span>
<span class="{main_posts.edited-class}"><i class="icon-edit visible-phone" title="edited by {main_posts.editorname} {main_posts.relativeEditTime} ago"></i></span>
<div class="post-buttons visible-phone">
<div class="post_reply"><i class="icon-reply"></i></div>
<button class="post_reply btn-link"><i class="icon-reply"></i></button>
</div>
<div class="post-buttons">
<a href="../{topic_id}.rss" target="_blank"><i class="icon-rss"></i></a>
</div>
</div>
</div>
@@ -75,22 +85,29 @@
<a href="/users/{posts.userslug}">
<img src="{posts.picture}" align="left" class="img-polaroid"/>
</a>
<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 class="stats">
<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>
</div>
<div class="span11">
<div class="post-block">
<div id="content_{posts.pid}" class="post-content">{posts.content}</div>
<div id="images_{posts.pid}" class="post-images">
<!-- BEGIN uploadedImages -->
<i class="icon-picture icon-1"></i><a href="{posts.uploadedImages.url}"> {posts.uploadedImages.name}</a><br/>
<!-- END uploadedImages -->
</div>
<div class="post-signature">{posts.signature}</div>
<div class="profile-block">
<span class="post-buttons">
<button id="ids_{posts.pid}_{posts.uid}" class="edit {posts.display_moderator_tools} hidden-phone" title="Edit"><i class="icon-pencil"></i></button>
<button id="ids_{posts.pid}_{posts.uid}" class="delete {posts.display_moderator_tools} hidden-phone" title="Delete"><i class="icon-trash"></i></button>
<button id="quote_{posts.pid}_{posts.uid}" class="quote hidden-phone" title="Quote"><i class="icon-quote-left"></i></button>
<button id="favs_{posts.pid}_{posts.uid}" class="favourite hidden-phone" title="Favourite"><span class="post_rep_{posts.pid}">{posts.post_rep} </span><i class="{posts.fav_star_class}"></i></button>
<button class="post_reply" title="Reply"><i class="icon-reply"></i></button>
<button id="ids_{posts.pid}_{posts.uid}" class="edit {posts.display_moderator_tools} btn-link hidden-phone" title="Edit"><i class="icon-pencil"></i></button>
<button id="ids_{posts.pid}_{posts.uid}" class="delete {posts.display_moderator_tools} btn-link hidden-phone" title="Delete"><i class="icon-trash"></i></button>
<button id="favs_{posts.pid}_{posts.uid}" class="favourite btn-link hidden-phone" title="Favourite"><span class="post_rep_{posts.pid}">{posts.post_rep} </span><i class="{posts.fav_star_class}"></i></button>
<button id="quote_{posts.pid}_{posts.uid}" class="quote btn-link hidden-phone" title="Quote"><i class="icon-quote-left"></i></button>
<button class="post_reply btn-link" title="Reply"><i class="icon-reply"></i></button>
</span>
<img class="hidden-desktop" src="{posts.picture}?s=10&default=identicon" align="left" /> posted by <strong><a class="username-field" href="/users/{posts.userslug}">{posts.username}</a></strong> {posts.relativeTime} ago
<img class="hidden-desktop" src="{posts.picture}" align="left" /> posted by <strong><a class="username-field" href="/users/{posts.userslug}">{posts.username}</a></strong> {posts.relativeTime} ago
<span class="{posts.edited-class} hidden-phone">| last edited by <strong><a href="/users/{posts.editorslug}">{posts.editorname}</a></strong> {posts.relativeEditTime} ago</span>
<span class="{posts.edited-class}"><i class="icon-edit visible-phone" title="edited by {posts.editorname} {posts.relativeEditTime} ago"></i></span>
</div>

0
public/themes/.gitignore vendored Normal file
View File

View File

@@ -41,25 +41,12 @@ var RDB = require('./../redis.js'),
var slug = cid + '/' + utils.slugify(category[key]);
RDB.hset('category:' + cid, 'slug', slug);
RDB.set('categoryslug:' + slug + ':cid', cid);
RDB.smembers('categories:' + cid + ':tid', function(err, tids) {
var pipe = RDB.multi();
for (var tid in tids) {
pipe.set(schema.topics(tid).category_name, category[key]);
pipe.set(schema.topics(tid).category_slug, slug);
}
pipe.exec();
});
}
}
updated.push(cid);
}
socket.emit('event:alert', {
title: 'Updated Categories',
message: 'Category IDs ' + updated.join(', ') + ' was successfully updated.',

View File

@@ -9,83 +9,94 @@ var RDB = require('./redis.js'),
Categories.getCategoryById = function(category_id, current_user, callback) {
Categories.getCategoryData(category_id, function(categoryData) {
if(!categoryData) {
callback(false);
return;
Categories.getCategoryData(category_id, function(err, categoryData) {
if (err) return callback(err);
var category_name = categoryData.name,
category_slug = categoryData.slug,
category_description = categoryData.description;
function getTopicIds(next) {
Categories.getTopicIds(category_id, next);
}
function getActiveUsers(next) {
Categories.getActiveUsers(category_id, next);
}
RDB.smembers('categories:' + category_id + ':tid', function(err, tids) {
var category_name = categoryData.name;
category_slug = categoryData.slug;
async.parallel([getTopicIds, getActiveUsers], function(err, results) {
var tids = results[0],
active_users = results[1];
RDB.smembers('cid:' + category_id + ':active_users', function(err, active_users) {
var categoryData = {
'category_name' : category_name,
'category_description': category_description,
'show_sidebar' : 'show',
'show_topic_button': 'inline-block',
'no_topics_message': 'hidden',
'topic_row_size': 'span9',
'category_id': category_id,
'active_users': [],
'topics' : [],
'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(global.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),
'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(global.nconf.get('url') + 'category/' + category_slug)
};
var categoryData = {
'category_name' : category_name,
'show_sidebar' : 'show',
'show_topic_button': 'show',
'no_topics_message': 'hidden',
'topic_row_size': 'span9',
'category_id': category_id,
'active_users': [],
'topics' : [],
'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(global.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),
'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(global.nconf.get('url') + 'category/' + category_slug)
};
function getTopics(next) {
Categories.getTopicsByTids(tids, current_user, function(topics) {
// Float pinned topics to the top
topics = topics.sort(function(a, b) {
if (a.pinned !== b.pinned) return b.pinned - a.pinned;
else {
return b.lastposttime - a.lastposttime;
}
});
next(null, topics);
}, category_id);
}
function getModerators(next) {
Categories.getModerators(category_id, function(moderators) {
next(null, moderators);
function getTopics(next) {
Categories.getTopicsByTids(tids, current_user, function(topics) {
// Float pinned topics to the top
topics = topics.sort(function(a, b) {
if (a.pinned !== b.pinned) return b.pinned - a.pinned;
else {
return b.lastposttime - a.lastposttime;
}
});
}
next(null, topics);
}, category_id);
}
function getModerators(next) {
Categories.getModerators(category_id, next);
}
function getActiveUsers(next) {
user.getMultipleUserFields(active_users, ['username', 'userslug', 'picture'], function(users) {
next(null, users);
});
}
function getActiveUsers(next) {
user.getMultipleUserFields(active_users, ['username', 'userslug', 'picture'], function(users) {
next(null, users);
});
}
if (tids.length === 0) {
getModerators(function(err, moderators) {
categoryData.moderator_block_class = moderators.length > 0 ? '' : 'none';
categoryData.moderators = moderators;
categoryData.show_sidebar = 'hidden';
categoryData.no_topics_message = 'show';
if (tids.length === 0) {
getModerators(function(err, moderators) {
categoryData.moderator_block_class = moderators.length > 0 ? '' : 'none';
categoryData.moderators = moderators;
categoryData.show_sidebar = 'hidden';
categoryData.no_topics_message = 'show';
callback(null, categoryData);
});
} else {
async.parallel([getTopics, getModerators, getActiveUsers], function(err, results) {
categoryData.topics = results[0];
categoryData.moderator_block_class = results[1].length > 0 ? '' : 'none';
categoryData.moderators = results[1];
categoryData.active_users = results[2];
callback(null, categoryData);
});
}
callback(categoryData);
});
} else {
async.parallel([getTopics, getModerators, getActiveUsers], function(err, results) {
categoryData.topics = results[0];
categoryData.moderator_block_class = results[1].length > 0 ? '' : 'none';
categoryData.moderators = results[1];
categoryData.active_users = results[2];
callback(categoryData);
});
}
});
});
});
}
Categories.getTopicIds = function(cid, callback) {
RDB.smembers('categories:' + cid + ':tid', callback);
}
Categories.getActiveUsers = function(cid, callback) {
RDB.smembers('cid:' + cid + ':active_users', callback);
}
// not the permanent location for this function
Categories.getLatestTopics = function(current_user, start, end, callback) {
RDB.zrevrange('topics:recent', 0, -1, function(err, tids) {
@@ -115,7 +126,6 @@ var RDB = require('./redis.js'),
Categories.getTopicsByTids = function(tids, current_user, callback, category_id /*temporary*/) {
var retrieved_topics = [];
var topicCountToLoad = tids.length;
function getTopicInfo(topicData, callback) {
@@ -164,10 +174,12 @@ var RDB = require('./redis.js'),
return !deleted || (deleted && topicInfo.privileges.view_deleted) || topicData.uid === current_user;
}
for(var i=0; i<tids.length; ++i) {
topics.getTopicData(tids[i], function(topicData) {
function loadTopic(tid, callback) {
topics.getTopicData(tid, function(topicData) {
if(!topicData) {
return callback(null);
}
getTopicInfo(topicData, function(topicInfo) {
topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none';
@@ -185,15 +197,17 @@ var RDB = require('./redis.js'),
if (isTopicVisible(topicData, topicInfo))
retrieved_topics.push(topicData);
else
--topicCountToLoad;
if(retrieved_topics.length === topicCountToLoad) {
callback(retrieved_topics);
}
callback(null);
});
});
}
async.eachSeries(tids, loadTopic, function(err) {
if(!err) {
callback(retrieved_topics);
}
});
}
@@ -204,16 +218,20 @@ var RDB = require('./redis.js'),
});
}
Categories.getModerators = function(cid, callback) {
RDB.smembers('cid:' + cid + ':moderators', function(err, mods) {
if (mods.length === 0)
return callback([]);
if(!err) {
if(mods && mods.length) {
user.getMultipleUserFields(mods, ['username'], function(moderators) {
callback(null, moderators);
});
} else {
callback(null, []);
}
} else {
callback(err, null);
}
user.getMultipleUserFields(mods, ['username'], function(moderators) {
callback(moderators);
});
});
}
@@ -297,14 +315,47 @@ var RDB = require('./redis.js'),
});
}
Categories.getCategoryData = function(cid, callback) {
RDB.hgetall('category:' + cid, function(err, data) {
if(err === null)
callback(data);
else
Categories.moveRecentReplies = function(tid, oldCid, cid, callback) {
topics.getPids(tid, function(err, pids) {
if(!err) {
function movePost(pid, callback) {
posts.getPostField(pid, 'timestamp', function(timestamp) {
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
});
}
async.each(pids, movePost, function(err) {
if(!err) {
callback(null, 1)
} else {
console.log(err);
callback(err, null);
}
});
} else {
console.log(err);
callback(err, null);
}
});
}
Categories.getCategoryData = function(cid, callback) {
RDB.exists('category:' + cid, function(err, exists) {
if (exists) RDB.hgetall('category:' + cid, callback);
else callback(new Error('No category found!'));
});
}
Categories.getCategoryFields = function(cid, fields, callback) {
RDB.hmgetObject('category:' + cid, fields, function(err, data) {
if(err === null)
callback(data);
else
console.log(err);
});
}
Categories.setCategoryField = function(cid, field, value) {
RDB.hset('category:' + cid, field, value);
@@ -315,32 +366,41 @@ var RDB = require('./redis.js'),
}
Categories.getCategories = function(cids, callback, current_user) {
if (!cids || cids.length === 0) {
if (!cids || !Array.isArray(cids) || cids.length === 0) {
callback({'categories' : []});
return;
}
var categories = [];
for(var i=0; i<cids.length; ++i) {
Categories.getCategoryData(cids[i], function(categoryData) {
function getCategory(cid, callback) {
Categories.getCategoryData(cid, function(err, categoryData) {
if(!categoryData)
if(err) {
callback(err);
return;
}
Categories.hasReadCategory(categoryData.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';
categories.push(categoryData);
if(categories.length === cids.length)
callback({'categories': categories});
callback(null);
}) ;
});
}
};
});
}
async.eachSeries(cids, getCategory, function(err) {
if(err) {
console.log(err);
callback(null);
return;
}
callback({'categories': categories});
});
};
}(exports));
}(exports));

View File

@@ -67,7 +67,12 @@
};
Feed.updateCategory = function(cid) {
categories.getCategoryById(cid, 0, function(categoryData) {
categories.getCategoryById(cid, 0, function(err, categoryData) {
if (err) {
console.log('Error: Could not update RSS feed for category ' + cid);
return;
}
var location = '/category/' + categoryData.category_id + '/' + categoryData.category_name,
xml_url = '/category' + cid + '.rss';

33
src/imgur.js Normal file
View File

@@ -0,0 +1,33 @@
var request = require('request');
(function(imgur) {
var clientID = '';
imgur.upload = function(image, type, callback) {
var options = {
url: 'https://api.imgur.com/3/upload.json',
headers: {
'Authorization': 'Client-ID ' + clientID
}
};
var post = request.post(options, function(err, req, body){
try{
callback(err, JSON.parse(body));
} catch(e) {
callback(err, body);
}
});
var upload = post.form({type:type, image:image});
}
imgur.setClientID = function(id) {
clientID = id;
}
}(exports));

View File

@@ -11,18 +11,19 @@ var async = require('async'),
'redis:host|Host IP or address of your Redis instance? (127.0.0.1)',
'redis:port|Host port of your Redis instance? (6379)',
'redis:password|Password of your Redis database? (no password)',
'secret|Your NodeBB secret? (keyboard mash for a bit here)',
'bcrypt_rounds|The number of rounds to use for bcrypt.genSalt? (10)'
'secret|Your NodeBB secret? (keyboard mash for a bit here)'
],
defaults: {
"base_url": 'http://localhost',
"port": 4567,
"use_port": true,
"redis:host": '127.0.0.1',
"redis:port": 6379,
"redis:password": '',
"redis": {
"host": '127.0.0.1',
"port": 6379,
"password": ''
},
"secret": utils.generateUUID(),
"bcrypt_rounds": 10,
"bcrypt_rounds": 12,
"upload_path": '/public/uploads'
},
ask: function(question, callback) {
@@ -40,7 +41,29 @@ var async = require('async'),
async.eachSeries(install.questions, function(question, next) {
var question = question.split('|');
install.ask(question[1], function(value) {
if (value !== '') config[question[0]] = value;
switch(question[0]) {
case 'use_port':
value = value.toLowerCase();
if (['y', 'yes', ''].indexOf(value) === -1) config[question[0]] = false;
break;
case 'redis:host':
config.redis = config.redis || {};
if (value !== '') config.redis.host = value;
break;
case 'redis:port':
config.redis = config.redis || {};
if (value !== '') config.redis.port = value;
break;
case 'redis:password':
config.redis = config.redis || {};
if (value !== '') config.redis.password = value;
break;
default:
if (value !== '') config[question[0]] = value;
break;
}
next();
});
}, function() {
@@ -58,6 +81,7 @@ var async = require('async'),
relative_path: relative_path
};
server_conf.base_url = protocol + '//' + host;
server_conf.relative_path = relative_path;
install.save(server_conf, client_conf, callback);

View File

@@ -1,7 +1,8 @@
var user = require('./user.js'),
bcrypt = require('bcrypt'),
RDB = require('./redis.js');
RDB = require('./redis.js'),
path = require('path');
(function(Login){
@@ -53,7 +54,7 @@ var user = require('./user.js'),
}
}
Login.loginViaTwitter = function(twid, handle, callback) {
Login.loginViaTwitter = function(twid, handle, photos, callback) {
user.get_uid_by_twitter_id(twid, function(uid) {
if (uid !== null) {
// Existing User
@@ -62,13 +63,22 @@ var user = require('./user.js'),
});
} else {
// New User
user.create(handle, null, null, function(err, uid) {
user.create(handle, undefined, undefined, function(err, uid) {
if (err !== null) {
callback(err);
} else {
// Save twitter-specific information to the user
user.setUserField(uid, 'twid', twid);
RDB.hset('twid:uid', twid, uid);
// Save their photo, if present
if (photos && photos.length > 0) {
var photoUrl = photos[0].value;
photoUrl = path.dirname(photoUrl) + '/' + path.basename(photoUrl, path.extname(photoUrl)).slice(0, -6) + 'bigger' + path.extname(photoUrl);
user.setUserField(uid, 'uploadedpicture', photoUrl);
user.setUserField(uid, 'picture', photoUrl);
}
callback(null, {
uid: uid
});
@@ -98,7 +108,7 @@ var user = require('./user.js'),
user.get_uid_by_email(email, function(uid) {
if (!uid) {
user.create(handle, null, email, function(err, uid) {
user.create(handle, undefined, email, function(err, uid) {
if (err !== null) {
callback(err);
} else success(uid);
@@ -129,7 +139,7 @@ var user = require('./user.js'),
user.get_uid_by_email(email, function(uid) {
if (!uid) {
user.create(name, null, email, function(err, uid) {
user.create(name, undefined, email, function(err, uid) {
if (err !== null) {
callback(err);
} else success(uid);

View File

@@ -1,6 +1,8 @@
var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
async = require('async');
async = require('async'),
path = require('path'),
fs = require('fs');
(function(Meta) {
Meta.config = {
@@ -42,4 +44,53 @@ var utils = require('./../public/src/utils.js'),
RDB.hdel('config', field);
}
}
Meta.themes = {
get: function(callback) {
var themePath = path.join(__dirname, '../', 'public/themes');
fs.readdir(themePath, function(err, files) {
var themeArr = [];
async.each(files, function(file, next) {
fs.lstat(path.join(themePath, file), function(err, stats) {
if(stats.isDirectory()) {
var themeDir = file,
themeConfPath = path.join(themePath, themeDir, 'theme.json');
fs.exists(themeConfPath, function(exists) {
if (exists) {
fs.readFile(themeConfPath, function(err, conf) {
conf = JSON.parse(conf);
conf.src = global.nconf.get('url') + 'themes/' + themeDir + '/' + conf.src;
if (conf.screenshot) conf.screenshot = global.nconf.get('url') + 'themes/' + themeDir + '/' + conf.screenshot;
else conf.screenshot = global.nconf.get('url') + 'images/themes/default.png';
themeArr.push(conf);
next();
});
}
});
} else next();
});
}, function(err) {
callback(err, themeArr);
});
});
},
saveViaGithub: function(repo_url, callback) {
// ...
}
}
Meta.build_title = function(title, current_user, callback) {
var user = require('./user');
if (!title) title = global.config.title || 'NodeBB';
else title += ' | ' + global.config.title || 'NodeBB';
// Grab the number of unread notifications
user.notifications.getUnreadCount(current_user, function(err, count) {
if (!err && count > 0) title = '(' + count + ') ' + title;
callback(err, title);
});
}
}(exports));

View File

@@ -4,13 +4,9 @@ var RDB = require('./redis.js'),
threadTools = require('./threadTools.js'),
user = require('./user.js'),
async = require('async'),
marked = require('marked'),
utils = require('../public/src/utils');
marked.setOptions({
breaks: true
});
(function(PostTools) {
PostTools.isMain = function(pid, tid, callback) {
RDB.lrange('tid:' + tid + ':posts', 0, 0, function(err, pids) {
@@ -67,7 +63,7 @@ marked.setOptions({
io.sockets.in('topic_' + tid).emit('event:post_edited', {
pid: pid,
title: title,
content: marked(content || '')
content: PostTools.markdownToHTML(content)
});
});
});
@@ -81,20 +77,28 @@ marked.setOptions({
}
PostTools.delete = function(uid, pid) {
var success = function() {
var success = function() {
posts.setPostField(pid, 'deleted', 1);
posts.getPostField(pid, 'tid', function(tid) {
io.sockets.in('topic_' + tid).emit('event:post_deleted', {
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
user.decrementUserFieldBy(postData.uid, 'postcount', 1);
io.sockets.in('topic_' + postData.tid).emit('event:post_deleted', {
pid: pid
});
// Delete the thread if it is the last undeleted post
threadTools.get_latest_undeleted_pid(tid, function(err, pid) {
threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) {
if (err && err.message === 'no-undeleted-pids-found') {
threadTools.delete(tid, -1, function(err) {
if (err) console.log('Error: Could not delete topic (tid: ' + tid + ')');
threadTools.delete(postData.tid, -1, function(err) {
if (err) console.log('Error: Could not delete topic (tid: ' + postData.tid + ')');
});
} else {
posts.getPostField(pid, 'timestamp', function(timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
}
});
});
@@ -108,13 +112,22 @@ marked.setOptions({
}
PostTools.restore = function(uid, pid) {
var success = function() {
var success = function() {
posts.setPostField(pid, 'deleted', 0);
posts.getPostField(pid, 'tid', function(tid) {
io.sockets.in('topic_' + tid).emit('event:post_restored', {
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
user.incrementUserFieldBy(postData.uid, 'postcount', 1);
io.sockets.in('topic_' + postData.tid).emit('event:post_restored', {
pid: pid
});
threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) {
posts.getPostField(pid, 'timestamp', function(timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
});
});
};
@@ -125,5 +138,36 @@ marked.setOptions({
});
}
PostTools.markdownToHTML = function(md, isSignature) {
var marked = require('marked'),
cheerio = require('cheerio');
marked.setOptions({
breaks: true
});
if (md && md.length > 0) {
var parsedContentDOM = cheerio.load(marked(md));
var domain = global.nconf.get('url');
parsedContentDOM('a').each(function() {
this.attr('rel', 'nofollow');
var href = this.attr('href');
if (href && !href.match(domain)) {
this.attr('href', domain + 'outgoing?' + href);
if (!isSignature) this.append(' <i class="icon-external-link"></i>');
}
});
html = parsedContentDOM.html();
} else {
html = '<p></p>';
}
return html;
}
}(exports));

View File

@@ -1,20 +1,18 @@
var RDB = require('./redis.js'),
utils = require('./../public/src/utils.js'),
schema = require('./schema.js'),
marked = require('marked'),
user = require('./user.js'),
topics = require('./topics.js'),
favourites = require('./favourites.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools'),
feed = require('./feed.js'),
async = require('async');
marked.setOptions({
breaks: true
});
(function(Posts) {
Posts.minimumPostLength = 8;
Posts.getPostsByTid = function(tid, start, end, callback) {
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
@@ -33,13 +31,14 @@ marked.setOptions({
}
Posts.addUserInfoToPost = function(post, callback) {
user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'picture', 'signature'], function(userData) {
user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature'], function(userData) {
post.username = userData.username || 'anonymous';
post.userslug = userData.userslug || '';
post.user_rep = userData.reputation || 0;
post.user_postcount = userData.postcount || 0;
post.picture = userData.picture || require('gravatar').url('', {}, https=global.nconf.get('https'));
post.signature = marked(userData.signature || '');
post.signature = postTools.markdownToHTML(userData.signature, true);
if(post.editor !== '') {
user.getUserFields(post.editor, ['username', 'userslug'], function(editorData) {
@@ -58,7 +57,7 @@ marked.setOptions({
var returnData = [];
function getPostSummary(pid, callback) {
Posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
Posts.addUserInfoToPost(postData, function() {
if(postData.deleted !== '1') {
@@ -79,52 +78,56 @@ marked.setOptions({
Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) {
if(err === null)
if(err === null) {
callback(data);
}
else
console.log(err);
});
}
Posts.getPostFields = function(uid, fields, callback) {
RDB.hmget('post:' + uid, fields, function(err, data) {
Posts.getPostFields = function(pid, fields, callback) {
RDB.hmgetObject('post:' + pid, fields, function(err, data) {
if(err === null) {
var returnData = {};
for(var i=0, ii=fields.length; i<ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(returnData);
callback(data);
}
else
else {
console.log(err);
}
});
}
Posts.getPostsByPids = function(pids, callback) {
var posts = [],
loaded = 0;
var posts = [];
for(var i=0, ii=pids.length; i<ii; ++i) {
(function(index, pid) {
Posts.getPostData(pid, function(postData) {
if(postData) {
postData.relativeTime = utils.relativeTime(postData.timestamp);
postData.post_rep = postData.reputation;
postData['edited-class'] = postData.editor !== '' ? '' : 'none';
postData['relativeEditTime'] = postData.edited !== '0' ? utils.relativeTime(postData.edited) : '';
postData.content = marked(postData.content || '');
posts[index] = postData;
}
function iterator(pid, callback) {
Posts.getPostData(pid, function(postData) {
if(postData) {
postData.relativeTime = utils.relativeTime(postData.timestamp);
postData.post_rep = postData.reputation;
postData['edited-class'] = postData.editor !== '' ? '' : 'none';
postData['relativeEditTime'] = postData.edited !== '0' ? utils.relativeTime(postData.edited) : '';
++loaded;
if(loaded === pids.length)
callback(posts);
});
}(i, pids[i]));
postData.content = postTools.markdownToHTML(postData.content);
if(postData.uploadedImages) {
postData.uploadedImages = JSON.parse(postData.uploadedImages);
} else {
postData.uploadedImages = [];
}
posts.push(postData);
}
callback(null);
});
}
async.eachSeries(pids, iterator, function(err) {
if(!err) {
callback(posts);
} else {
callback([]);
}
});
}
Posts.getPostField = function(pid, field, callback) {
@@ -170,7 +173,21 @@ marked.setOptions({
});
}
Posts.reply = function(socket, tid, uid, content) {
Posts.emitContentTooShortAlert = function(socket) {
socket.emit('event:alert', {
type: 'error',
timeout: 2000,
title: 'Content too short',
message: "Please enter a longer post. At least " + Posts.minimumPostLength + " characters.",
alert_id: 'post_error'
});
}
Posts.reply = function(socket, tid, uid, content, images) {
if(content) {
content = content.trim();
}
if (uid < 1) {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
@@ -179,9 +196,11 @@ marked.setOptions({
timeout: 2000
});
return;
} else if (!content || content.length < Posts.minimumPostLength) {
Posts.emitContentTooShortAlert(socket);
return;
}
user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(Date.now() - lastposttime < config.post_delay) {
@@ -194,13 +213,13 @@ marked.setOptions({
return;
}
Posts.create(uid, tid, content, function(pid) {
if (pid > 0) {
RDB.rpush('tid:' + tid + ':posts', pid);
Posts.create(uid, tid, content, images, function(postData) {
if (postData) {
topics.addPostToTopic(tid, postData.pid);
topics.markUnRead(tid);
RDB.del('tid:' + tid + ':read_by_uid');
Posts.get_cid_by_pid(pid, function(cid) {
Posts.get_cid_by_pid(postData.pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
topics.markAsRead(tid, uid);
});
@@ -211,7 +230,6 @@ marked.setOptions({
// Send notifications to users who are following this topic
threadTools.notify_followers(tid, uid);
socket.emit('event:alert', {
title: 'Reply Successful',
message: 'You have successfully replied. Click here to view your reply.',
@@ -219,21 +237,16 @@ marked.setOptions({
timeout: 2000
});
postData.content = postTools.markdownToHTML(postData.content);
postData.post_rep = 0;
postData.relativeTime = utils.relativeTime(postData.timestamp)
postData.fav_star_class = 'icon-star-empty';
postData['edited-class'] = 'none';
postData.uploadedImages = JSON.parse(postData.uploadedImages);
var timestamp = Date.now();
var socketData = {
'posts' : [
{
'pid' : pid,
'content' : marked(content || ''),
'uid' : uid,
'post_rep' : 0,
'timestamp' : timestamp,
'relativeTime': utils.relativeTime(timestamp),
'fav_star_class' :'icon-star-empty',
'edited-class': 'none',
'editor': '',
}
postData
]
};
@@ -241,6 +254,7 @@ marked.setOptions({
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData);
});
} else {
socket.emit('event:alert', {
@@ -253,10 +267,10 @@ marked.setOptions({
});
});
};
Posts.create = function(uid, tid, content, callback) {
Posts.create = function(uid, tid, content, images, callback) {
if (uid === null) {
callback(-1);
callback(null);
return;
}
@@ -268,7 +282,7 @@ marked.setOptions({
var timestamp = Date.now();
RDB.hmset('post:' + pid, {
var postData = {
'pid': pid,
'uid': uid,
'tid': tid,
@@ -277,12 +291,14 @@ marked.setOptions({
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0
});
'deleted': 0,
'uploadedImages': ''
};
RDB.hmset('post:' + pid, postData);
topics.increasePostCount(tid);
topics.setTopicField(tid, 'lastposttime', timestamp);
topics.addToRecent(tid, timestamp);
topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount');
@@ -301,17 +317,52 @@ marked.setOptions({
RDB.sadd('cid:' + cid + ':active_users', uid);
});
});
});
user.onNewPostMade(uid, tid, pid, timestamp);
if (callback)
callback(pid);
var imgur = require('./imgur');
// move clientID to config
imgur.setClientID('09f3955fee9a0a6');
var uploadedImages = [];
function uploadImage(image, callback) {
imgur.upload(image.data, 'base64', function(err, data) {
if(err) {
callback(err);
} else {
if(data.success) {
var img= {url:data.data.link, name:image.name};
uploadedImages.push(img);
callback(null);
} else {
callback(data);
}
}
});
}
if(!images) {
postData.uploadedImages = JSON.stringify(uploadedImages);
Posts.setPostField(pid, 'uploadedImages', postData.uploadedImages);
callback(postData);
} else {
async.each(images, uploadImage, function(err) {
if(!err) {
postData.uploadedImages = JSON.stringify(uploadedImages);
Posts.setPostField(pid, 'uploadedImages', postData.uploadedImages);
callback(postData);
} else {
console.log(err);
callback(null);
}
});
}
});
} else {
callback(-1);
callback(null);
}
});
}

View File

@@ -37,28 +37,26 @@
};
/*
* A lot of redis calls come back like this:
* [key, value, key, value, key, value]
* this is a simple utility fn to turn this into an object.
*/
RedisDB.exports.objectify = function(arr) {
var obj = {};
for (var i = 0; i < arr.length; i += 2) {
obj[arr[i]] = arr[i+1];
}
return obj;
};
* gets fields of a hash as an object instead of an array
*/
RedisDB.exports.hmgetObject = function (key, fields, callback) {
RedisDB.exports.hmget(key, fields, function(err, data) {
if(err === null) {
var returnData = {};
for(var i=0, ii=fields.length; i<ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
}
else {
console.log(err);
callback(err, null);
}
});
}
/*
* Similar to .objectify, this utility function splits the data array into two arrays
*/
RedisDB.exports.splitify = function(arr) {
var arr1 = [], arr2 = [];
for (var i = 0; i < arr.length; i += 2) {
arr1.push(arr[i]);
arr2.push(arr[i+1]);
}
return [arr1,arr2];
};
}(module));

View File

@@ -1,7 +1,8 @@
var user = require('./../user.js'),
topics = require('./../topics.js'),
RDB = require('./../redis.js')
RDB = require('./../redis.js'),
pkg = require('./../../package.json'),
categories = require('./../categories.js');
(function(Admin) {
@@ -14,7 +15,6 @@ var user = require('./../user.js'),
Admin.build_header = function(res) {
return templates['admin/header'].parse({
cssSrc: global.config['theme:src'] || global.nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css',
csrf:res.locals.csrf_token,
relative_path: global.nconf.get('relative_path')
});
@@ -23,7 +23,8 @@ var user = require('./../user.js'),
Admin.create_routes = function(app) {
(function() {
var routes = ['categories', 'users', 'topics', 'settings', 'themes', 'twitter', 'facebook', 'gplus', 'redis', 'motd'];
var routes = ['categories', 'users', 'topics', 'settings', 'themes', 'twitter', 'facebook', 'gplus', 'redis', 'motd',
'users/latest', 'users/sort-posts', 'users/sort-reputation', 'users/search'];
for (var i=0, ii=routes.length; i<ii; i++) {
(function(route) {
@@ -57,6 +58,9 @@ var user = require('./../user.js'),
function api_method(req, res) {
switch(req.params.method) {
case 'index':
res.json({version:pkg.version});
break;
case 'users' :
if (req.params.tab == 'search') {
res.json({search_display: 'block', users: []});

View File

@@ -5,8 +5,8 @@
passportGoogle = require('passport-google-oauth').OAuth2Strategy,
passportFacebook = require('passport-facebook').Strategy,
login_strategies = [],
user_module = require('./../user.js'),
nconf = require('nconf'),
users = require('../user'),
login_module = require('./../login.js');
passport.use(new passportLocal(function(user, password, next) {
@@ -20,9 +20,9 @@
passport.use(new passportTwitter({
consumerKey: global.config['social:twitter:key'],
consumerSecret: global.config['social:twitter:secret'],
callbackURL: config.url + 'auth/twitter/callback'
callbackURL: nconf.get('url') + 'auth/twitter/callback'
}, function(token, tokenSecret, profile, done) {
login_module.loginViaTwitter(profile.id, profile.username, function(err, user) {
login_module.loginViaTwitter(profile.id, profile.username, profile.photos, function(err, user) {
if (err) { return done(err); }
done(null, user);
});
@@ -35,7 +35,7 @@
passport.use(new passportGoogle({
clientID: global.config['social:google:id'],
clientSecret: global.config['social:google:secret'],
callbackURL: config.url + 'auth/google/callback'
callbackURL: nconf.get('url') + 'auth/google/callback'
}, function(accessToken, refreshToken, profile, done) {
login_module.loginViaGoogle(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
if (err) { return done(err); }
@@ -50,7 +50,7 @@
passport.use(new passportFacebook({
clientID: global.config['social:facebook:app_id'],
clientSecret: global.config['social:facebook:secret'],
callbackURL: config.url + 'auth/facebook/callback'
callbackURL: nconf.get('url') + 'auth/facebook/callback'
}, function(accessToken, refreshToken, profile, done) {
login_module.loginViaFacebook(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
if (err) { return done(err); }
@@ -84,11 +84,15 @@
Auth.create_routes = function(app) {
app.get('/logout', function(req, res) {
console.log('info: [Auth] Session ' + req.sessionID + ' logout (uid: ' + global.uid + ')');
login_module.logout(req.sessionID, function(logout) {
req.logout();
res.send(app.build_header(res) + templates['logout'] + templates['footer']);
});
if (req.user && req.user.uid > 0) {
console.log('info: [Auth] Session ' + req.sessionID + ' logout (uid: ' + req.user.uid + ')');
login_module.logout(req.sessionID, function(logout) {
req.logout();
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + templates['logout'] + templates['footer']);
});
});
} else res.redirect('/');
});
if (login_strategies.indexOf('twitter') !== -1) {
@@ -121,11 +125,16 @@
app.get('/reset/:code', function(req, res) {
res.send(app.build_header(res) + templates['reset_code'].parse({ reset_code: req.params.code }) + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + templates['reset_code'].parse({ reset_code: req.params.code }) + templates['footer']);
});
});
app.get('/reset', function(req, res) {
res.send(app.build_header(res) + templates['reset'] + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
console.log(header);
res.send(header + templates['reset'] + templates['footer']);
});
});
@@ -134,7 +143,7 @@
});
app.post('/register', function(req, res) {
user_module.create(req.body.username, req.body.password, req.body.email, function(err, uid) {
users.create(req.body.username, req.body.password, req.body.email, function(err, uid) {
if (err === null && uid > 0) {
req.login({

View File

@@ -1,5 +1,6 @@
var user = require('./../user.js'),
posts = require('./../posts.js'),
postTools = require('../postTools'),
fs = require('fs'),
utils = require('./../../public/src/utils.js'),
path = require('path'),
@@ -24,23 +25,33 @@ var user = require('./../user.js'),
});
app.get('/users', function(req, res) {
res.send(app.build_header(res) + app.create_route("users", "users") + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users", "users") + templates['footer']);
});
});
app.get('/users-latest', function(req, res) {
res.send(app.build_header(res) + app.create_route("users-latest", "users") + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-latest", "users") + templates['footer']);
});
});
app.get('/users-sort-posts', function(req, res) {
res.send(app.build_header(res) + app.create_route("users-sort-posts", "users") + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-sort-posts", "users") + templates['footer']);
});
});
app.get('/users-sort-reputation', function(req, res) {
res.send(app.build_header(res) + app.create_route("users-sort-reputation", "users") + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-sort-reputation", "users") + templates['footer']);
});
});
app.get('/users-search', function(req, res) {
res.send(app.build_header(res) + app.create_route("users-search", "users") + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-search", "users") + templates['footer']);
});
});
app.get('/users/:userslug', function(req, res, next) {
@@ -56,7 +67,9 @@ var user = require('./../user.js'),
return;
}
res.send(app.build_header(res) + app.create_route('users/'+req.params.userslug, 'account') + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug, 'account') + templates['footer']);
});
});
});
@@ -68,7 +81,25 @@ var user = require('./../user.js'),
user.getUserField(req.user.uid, 'userslug', function(userslug) {
if(req.params.userslug && userslug === req.params.userslug) {
res.send(app.build_header(res) + app.create_route('users/'+req.params.userslug+'/edit','accountedit') + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/edit','accountedit') + templates['footer']);
});
} else {
return res.redirect('/404');
}
});
});
app.get('/users/:userslug/settings', function(req, res) {
if(!req.user)
return res.redirect('/403');
user.getUserField(req.user.uid, 'userslug', function(userslug) {
if(req.params.userslug && userslug === req.params.userslug) {
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/settings','accountsettings') + templates['footer']);
})
} else {
return res.redirect('/404');
}
@@ -96,21 +127,19 @@ var user = require('./../user.js'),
}
user.getUserField(req.user.uid, 'uploadedpicture', function(oldpicture) {
if(!oldpicture) {
uploadUserPicture(req.user.uid, 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;
}
var absolutePath = path.join(global.configuration['ROOT_DIRECTORY'], config.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) {
if(err) {
if(err) {
console.error('[%d] %s', Date.now(), + err);
}
uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res);
});
});
});
@@ -124,7 +153,7 @@ var user = require('./../user.js'),
}
var filename = uid + '-profileimg' + extension;
var uploadPath = path.join(global.configuration['ROOT_DIRECTORY'], config.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
console.log('Info: Attempting upload to: '+ uploadPath);
@@ -135,20 +164,17 @@ var user = require('./../user.js'),
is.on('end', function() {
fs.unlinkSync(tempPath);
var imageUrl = config.upload_url + filename;
res.json({ path: imageUrl });
var imageUrl = global.nconf.get('upload_url') + filename;
user.setUserField(uid, 'uploadedpicture', imageUrl);
user.setUserField(uid, 'picture', imageUrl);
var im = require('node-imagemagick');
im.resize({
require('node-imagemagick').crop({
srcPath: uploadPath,
dstPath: uploadPath,
width: 128
}, function(err, stdout, stderr) {
dstPath: uploadPath,
width: 128,
height: 128
}, function(err, stdout, stderr){
if (err) {
// @todo: better logging method; for now, send to stderr.
// ideally, this should be happening in another process
@@ -156,6 +182,8 @@ var user = require('./../user.js'),
// to crash the main process
console.error('[%d] %s', Date.now(), + err);
}
res.json({ path: imageUrl });
});
});
@@ -178,7 +206,9 @@ var user = require('./../user.js'),
return;
}
res.send(app.build_header(res) + app.create_route('users/'+req.params.userslug+'/following','following') + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/following','following') + templates['footer']);
});
});
});
@@ -192,13 +222,16 @@ var user = require('./../user.js'),
res.redirect('/404');
return;
}
res.send(app.build_header(res) + app.create_route('users/'+req.params.userslug+'/followers','followers') + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/followers','followers') + templates['footer']);
});
});
});
function api_method(req, res) {
var callerUID = req.user ? req.user.uid : 0;
var userslug = req.params.userslug;
var section = req.params.section ? String(req.params.section).toLowerCase() : null;
@@ -221,14 +254,18 @@ var user = require('./../user.js'),
getUserDataByUserSlug(userslug, callerUID, function(userData) {
res.json(userData);
});
}
else if (section === 'settings') {
getSettings(req, res, callerUID);
} else {
getUserDataByUserSlug(userslug, callerUID, function(userData) {
if(userData) {
user.isFollowing(callerUID, userData.theirid, function(isFollowing) {
posts.getPostsByUid(userData.theirid, 0, 9, function(posts) {
userData.posts = posts;
userData.posts = posts.filter(function(p) {return p.deleted !== "1";});
userData.isFollowing = isFollowing;
userData.signature = marked(userData.signature || '');
userData.signature = postTools.markdownToHTML(userData.signature, true);
res.json(userData);
});
});
@@ -268,6 +305,32 @@ var user = require('./../user.js'),
});
}
function getSettings(req, res, callerUid) {
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','showemail'], function(userData) {
if(userData) {
if(userData.showemail && userData.showemail === "1")
userData.showemail = "checked";
else
userData.showemail = "";
res.json(userData);
} else {
res.json(404, { error: 'User not found!' }) ;
}
});
});
}
app.get('/api/users/:userslug?/:section?', api_method);
app.get('/api/users-sort-posts', getUsersSortedByPosts);
app.get('/api/users-sort-reputation', getUsersSortedByReputation);
@@ -312,7 +375,7 @@ var user = require('./../user.js'),
callback(null);
return;
}
user.getUserData(uid, function(data) {
if(data) {
data.joindate = utils.relativeTime(data.joindate);
@@ -323,6 +386,19 @@ var user = require('./../user.js'),
data.age = new Date().getFullYear() - new Date(data.birthday).getFullYear();
}
function canSeeEmail() {
return callerUID === uid || (data.email && (data.showemail && data.showemail === "1"));
}
if(!canSeeEmail())
data.email = "";
if(callerUID === uid && data.showemail === "0")
data.emailClass = "";
else
data.emailClass = "hide";
data.uid = uid;
data.yourid = callerUID;
data.theirid = uid;
@@ -341,10 +417,7 @@ var user = require('./../user.js'),
});
}
};
}(exports));

70
src/sitemap.js Normal file
View File

@@ -0,0 +1,70 @@
var path = require('path'),
async = require('async'),
sm = require('sitemap'),
url = require('url'),
categories = require('./categories'),
topics = require('./topics'),
sitemap = {
getStaticUrls: function(callback) {
callback(null, [
{ url: '', changefreq: 'weekly', priority: '0.6' },
{ url: 'recent', changefreq: 'daily', priority: '0.4' },
{ url: 'users', changefreq: 'daily', priority: '0.4' }
]);
},
getDynamicUrls: function(callback) {
var returnUrls = [];
async.parallel([
function(next) {
var categoryUrls = [];
categories.getAllCategories(function(data) {
data.categories.forEach(function(category) {
categoryUrls.push({
url: path.join('category', category.slug),
changefreq: 'weekly',
priority: '0.4'
});
});
next(null, categoryUrls);
}, 0);
},
function(next) {
var topicUrls = [];
topics.getAllTopics(null, null, function(topics) {
topics.forEach(function(topic) {
topicUrls.push({
url: path.join('topic', topic.slug),
changefreq: 'daily',
priority: '0.6'
});
});
next(null, topicUrls);
});
}
], function(err, data) {
if (!err) {
returnUrls = returnUrls.concat(data[0]).concat(data[1]);
}
callback(null, returnUrls);
});
},
render: function(callback) {
async.parallel([sitemap.getStaticUrls, sitemap.getDynamicUrls], function(err, urls) {
var urls = urls[0].concat(urls[1]),
map = sm.createSitemap({
hostname: global.nconf.get('url'),
cacheTime: 600000,
urls: urls
}),
xml = map.toXML(function(xml) {
callback(xml);
});
});
}
}
module.exports = sitemap;

View File

@@ -171,14 +171,15 @@ var RDB = require('./redis.js'),
topics.setTopicField(tid, 'cid', cid);
categories.moveRecentReplies(tid, oldCid, cid, function(err, data) {
if(err) {
console.log(err);
}
});
categories.incrementCategoryFieldBy(oldCid, 'topic_count', -1);
categories.incrementCategoryFieldBy(cid, 'topic_count', 1);
categories.getCategories([cid], function(data) {
topics.setTopicField(tid, 'category_name', data.categories[0].name);
topics.setTopicField(tid, 'category_slug', data.categories[0].slug);
});
socket.emit('api:topic.move', {
status: 'ok'
});

View File

@@ -18,6 +18,8 @@ marked.setOptions({
(function(Topics) {
Topics.minimumTitleLength = 3;
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
if(err === null)
@@ -51,16 +53,15 @@ marked.setOptions({
}
function addUserInfoToPosts(next) {
var done = 0;
for(var i=0, ii=postData.length; i<ii; ++i) {
posts.addUserInfoToPost(postData[i], function() {
++done;
if(done === postData.length)
next(null, null);
function iterator(post, callback) {
posts.addUserInfoToPost(post, function() {
callback(null);
});
}
async.each(postData, iterator, function(err) {
next(err, null);
});
}
function getPrivileges(next) {
@@ -83,9 +84,16 @@ marked.setOptions({
});
}
Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(cid) {
categories.getCategoryData(cid, callback);
});
}
Topics.getTopicWithPosts = function(tid, current_user, callback) {
threadTools.exists(tid, function(exists) {
if (!exists) return callback(new Error('Topic tid \'' + tid + '\' not found'));
if (!exists)
return callback(new Error('Topic tid \'' + tid + '\' not found'));
Topics.markAsRead(tid, current_user);
@@ -107,19 +115,29 @@ marked.setOptions({
next(null, privData);
});
}
function getCategoryData(next) {
Topics.getCategoryData(tid, next);
}
async.parallel([getTopicData, getTopicPosts, getPrivileges], function(err, results) {
if (err) console.log(err.message);
async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData], function(err, results) {
if (err) {
console.log(err.message);
callback(err, null);
return;
}
var topicData = results[0],
topicPosts = results[1],
privileges = results[2];
privileges = results[2],
categoryData = results[3];
var main_posts = topicPosts.splice(0, 1);
callback(null, {
'topic_name':topicData.title,
'category_name':topicData.category_name,
'category_slug':topicData.category_slug,
'category_name':categoryData.name,
'category_slug':categoryData.slug,
'locked': topicData.locked,
'deleted': topicData.deleted,
'pinned': topicData.pinned,
@@ -173,7 +191,8 @@ marked.setOptions({
topicData.teaser_text = teaser.text || '';
topicData.teaser_username = teaser.username || '';
topicData.teaser_timestamp = teaser.timestamp ? utils.relativeTime(teaser.timestamp) : '';
topicData.teaser_userpicture = teaser.picture;
callback(topicData);
});
}
@@ -224,6 +243,10 @@ marked.setOptions({
});
}
Topics.markUnRead = function(tid) {
RDB.del('tid:' + tid + ':read_by_uid');
}
Topics.markAsRead = function(tid, uid) {
RDB.sadd(schema.topics(tid).read_by_uid, uid);
@@ -265,7 +288,7 @@ marked.setOptions({
Topics.getTeasers = function(tids, callback) {
var teasers = [];
if (Array.isArray(tids)) {
async.each(tids, function(tid, next) {
async.eachSeries(tids, function(tid, next) {
Topics.getTeaser(tid, function(err, teaser_info) {
if (err) teaser_info = {};
teasers.push(teaser_info);
@@ -287,7 +310,7 @@ marked.setOptions({
timestamp = postData.timestamp;
if(postData.content)
stripped = utils.strip_tags(marked(postData.content));
stripped = utils.strip_tags(postTools.markdownToHTML(postData.content));
callback(null, {
"text": stripped,
@@ -301,10 +324,25 @@ marked.setOptions({
});
}
Topics.post = function(socket, uid, title, content, category_id) {
Topics.emitTitleTooShortAlert = function(socket) {
socket.emit('event:alert', {
type: 'error',
timeout: 2000,
title: 'Title too short',
message: "Please enter a longer title. At least " + Topics.minimumTitleLength + " characters.",
alert_id: 'post_error'
});
}
Topics.post = function(socket, uid, title, content, category_id, images) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if(content)
content = content.trim();
if(title)
title = title.trim();
if (uid === 0) {
socket.emit('event:alert', {
title: 'Thank you for posting',
@@ -316,6 +354,12 @@ marked.setOptions({
}
});
return; // for now, until anon code is written.
} else if(!title || title.length < Topics.minimumTitleLength) {
Topics.emitTitleTooShortAlert(socket);
return;
} else if (!content || content.length < posts.miminumPostLength) {
posts.emitContentTooShortAlert(socket);
return;
}
user.getUserField(uid, 'lastposttime', function(lastposttime) {
@@ -361,9 +405,9 @@ marked.setOptions({
RDB.set('topicslug:' + slug + ':tid', tid);
posts.create(uid, tid, content, function(pid) {
if (pid > 0) {
RDB.lpush(schema.topics(tid).posts, pid);
posts.create(uid, tid, content, images, function(postData) {
if (postData) {
RDB.lpush(schema.topics(tid).posts, postData.pid);
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
@@ -388,12 +432,6 @@ marked.setOptions({
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.sadd('categories:' + category_id + ':tid', tid);
categories.getCategories([category_id], function(data) {
Topics.setTopicField(tid, 'category_name', data.categories[0].name);
Topics.setTopicField(tid, 'category_slug', data.categories[0].slug);
});
RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
@@ -432,8 +470,17 @@ marked.setOptions({
});
}
Topics.addToRecent = function(tid, timestamp) {
Topics.updateTimestamp = function(tid, timestamp) {
RDB.zadd(schema.topics().recent, timestamp, tid);
Topics.setTopicField(tid, 'lastposttime', timestamp);
}
Topics.addPostToTopic = function(tid, pid) {
RDB.rpush('tid:' + tid + ':posts', pid);
}
Topics.getPids = function(tid, callback) {
RDB.lrange('tid:' + tid + ':posts', 0, -1, callback);
}
}(exports));

View File

@@ -10,81 +10,90 @@ var utils = require('./../public/src/utils.js'),
async = require('async');
(function(User) {
User.create = function(username, password, email, callback) {
username = username.trim(), email = email.trim();
// @todo use node-validator?
if(!utils.isEmailValid(email) || !utils.isUserNameValid(username) || !utils.isPasswordValid(password)) {
callback('Invalid email/username/password!', 0);
return;
}
var userslug = utils.slugify(username);
User.exists(userslug, function(exists) {
if(exists) {
callback('Username taken!', 0);
return;
username = username.trim();
if (email !== undefined) email = email.trim();
async.parallel([
function(next) {
if (email !== undefined) next(!utils.isEmailValid(email) ? new Error('Invalid Email!') : null);
else next();
},
function(next) {
next(!utils.isUserNameValid(username) ? new Error('Invalid Username!') : null);
},
function(next) {
if (password !== undefined) next(!utils.isPasswordValid(password) ? new Error('Invalid Password!') : null);
else next();
},
function(next) {
User.exists(userslug, function(exists) {
next(exists ? new Error('Username taken!') : null);
});
},
function(next) {
if (email !== undefined) {
User.isEmailAvailable(email, function(available) {
next(!available ? new Error('Email taken!') : null);
});
} else next();
}
User.isEmailAvailable(email, function(available) {
if(!available) {
callback('Email taken!', 0);
return;
], function(err, results) {
if (err) return callback(err, 0); // FIXME: Maintaining the 0 for backwards compatibility. Do we need this?
RDB.incr('global:next_user_id', function(err, uid) {
RDB.handle(err);
var gravatar = User.createGravatarURLFromEmail(email);
RDB.hmset('user:'+uid, {
'uid': uid,
'username' : username,
'userslug' : userslug,
'fullname': '',
'location':'',
'birthday':'',
'website':'',
'email' : email || '',
'signature':'',
'joindate' : Date.now(),
'picture': gravatar,
'gravatarpicture' : gravatar,
'uploadedpicture': '',
'reputation': 0,
'postcount': 0,
'lastposttime': 0,
'administrator': (uid == 1) ? 1 : 0,
'showemail': 0
});
RDB.set('username:' + username + ':uid', uid);
RDB.set('userslug:'+ userslug +':uid', uid);
if (email !== undefined) {
RDB.set('email:' + email +':uid', uid);
User.sendConfirmationEmail(email);
}
RDB.incr('global:next_user_id', function(err, uid) {
RDB.incr('usercount', function(err, count) {
RDB.handle(err);
var gravatar = User.createGravatarURLFromEmail(email);
RDB.hmset('user:'+uid, {
'uid': uid,
'username' : username,
'userslug' : userslug,
'fullname': '',
'location':'',
'birthday':'',
'website':'',
'email' : email,
'signature':'',
'joindate' : Date.now(),
'picture': gravatar,
'gravatarpicture' : gravatar,
'uploadedpicture': '',
'reputation': 0,
'postcount': 0,
'lastposttime': 0,
'administrator': (uid == 1) ? 1 : 0
});
RDB.set('username:' + username + ':uid', uid);
RDB.set('email:' + email +':uid', uid);
RDB.set('userslug:'+ userslug +':uid', uid);
if(email) {
User.sendConfirmationEmail(email);
}
RDB.incr('usercount', function(err, count) {
RDB.handle(err);
io.sockets.emit('user.count', {count: count});
});
RDB.lpush('userlist', uid);
io.sockets.emit('user.latest', {userslug: userslug, username: username});
if (password) {
User.hashPassword(password, function(hash) {
User.setUserField(uid, 'password', hash);
});
}
callback(null, uid);
io.sockets.emit('user.count', {count: count});
});
RDB.lpush('userlist', uid);
io.sockets.emit('user.latest', {userslug: userslug, username: username});
if (password !== undefined) {
User.hashPassword(password, function(hash) {
User.setUserField(uid, 'password', hash);
});
}
callback(null, uid);
});
});
};
@@ -124,13 +133,9 @@ var utils = require('./../public/src/utils.js'),
}
User.getUserFields = function(uid, fields, callback) {
RDB.hmget('user:' + uid, fields, function(err, data) {
RDB.hmgetObject('user:' + uid, fields, function(err, data) {
if(err === null) {
for(var i = 0, returnData = {}, ii=fields.length; i<ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(returnData);
callback(data);
} else {
console.log(err);
}
@@ -139,7 +144,7 @@ var utils = require('./../public/src/utils.js'),
User.getMultipleUserFields = function(uids, fields, callback) {
if(uids.length === 0) {
callback({});
callback([]);
return;
}
@@ -156,7 +161,7 @@ var utils = require('./../public/src/utils.js'),
});
}
async.each(uuids, iterator, function(err) {
async.eachSeries(uuids, iterator, function(err) {
if(!err) {
callback(returnData);
} else {
@@ -312,10 +317,18 @@ var utils = require('./../public/src/utils.js'),
RDB.hset('user:' + uid, field, value);
}
User.setUserFields = function(uid, data) {
RDB.hmset('user:' + uid, data);
}
User.incrementUserFieldBy = function(uid, field, value) {
RDB.hincrby('user:' + uid, field, value);
}
User.decrementUserFieldBy = function(uid, field, value) {
RDB.hincrby('user:' + uid, field, -value);
}
User.getUserList = function(callback) {
var data = [];
@@ -325,7 +338,9 @@ var utils = require('./../public/src/utils.js'),
function iterator(uid, callback) {
User.getUserData(uid, function(userData) {
data.push(userData);
if(userData) {
data.push(userData);
}
callback(null);
});
}
@@ -552,7 +567,7 @@ var utils = require('./../public/src/utils.js'),
});
}
async.each(uids, iterator, function(err) {
async.eachSeries(uids, iterator, function(err) {
callback(returnData);
});
}
@@ -637,7 +652,7 @@ var utils = require('./../public/src/utils.js'),
});
}
async.each(uids, iterator, function(err) {
async.eachSeries(uids, iterator, function(err) {
callback(usernames);
});
}
@@ -656,7 +671,7 @@ var utils = require('./../public/src/utils.js'),
});
}
async.each(uids, iterator, function(err) {
async.eachSeries(uids, iterator, function(err) {
callback(userslugs);
});
}
@@ -726,14 +741,14 @@ var utils = require('./../public/src/utils.js'),
User.isModerator = function(uid, cid, callback) {
RDB.sismember('cid:' + cid + ':moderators', uid, function(err, exists) {
// @todo handle error
RDB.handle(err);
callback(!!exists);
});
}
User.isAdministrator = function(uid, callback) {
RDB.sismember('administrators', uid, function(err, exists) {
// @todo handle error
RDB.handle(err);
callback(!!exists);
});
}
@@ -904,29 +919,6 @@ var utils = require('./../public/src/utils.js'),
}
};
User.get_online_users = function(socket, uids) {
RDB.sismembers('users:online', uids, function(err, data) {
RDB.handle(err);
socket.emit('api:user.get_online_users', data);
});
};
User.go_online = function(uid) {
RDB.sadd('users:online', uid, function(err) {
if (err) {
RDB.handle(err);
}
});
};
User.go_offline = function(uid) {
RDB.srem('users:online', uid, function(err) {
if (err) {
RDB.handle(err);
}
});
};
User.active = {
get_record : function(socket) {
RDB.mget(['global:active_user_record', 'global:active_user_record_date'], function(err, data) {
@@ -1000,11 +992,17 @@ var utils = require('./../public/src/utils.js'),
User.notifications = {
get: function(uid, callback) {
var maxNotifs = 15;
async.parallel({
unread: function(next) {
RDB.zrevrangebyscore('uid:' + uid + ':notifications:unread', 10, 0, function(err, nids) {
// @todo handle err
var unread = [];
// Cap the number of notifications returned
if (nids.length > maxNotifs) nids.length = maxNotifs;
if (nids && nids.length > 0) {
async.eachSeries(nids, function(nid, next) {
notifications.get(nid, function(notif_data) {
@@ -1023,6 +1021,10 @@ var utils = require('./../public/src/utils.js'),
RDB.zrevrangebyscore('uid:' + uid + ':notifications:read', 10, 0, function(err, nids) {
// @todo handle err
var read = [];
// Cap the number of notifications returned
if (nids.length > maxNotifs) nids.length = maxNotifs;
if (nids && nids.length > 0) {
async.eachSeries(nids, function(nid, next) {
notifications.get(nid, function(notif_data) {
@@ -1039,19 +1041,32 @@ var utils = require('./../public/src/utils.js'),
}
}, function(err, notifications) {
// While maintaining score sorting, sort by time
var readCount = notifications.read.length,
unreadCount = notifications.unread.length;
notifications.read.sort(function(a, b) {
if (a.score === b.score) {
return (a.datetime - b.datetime) > 0 ? -1 : 1;
}
});
notifications.unread.sort(function(a, b) {
if (a.score === b.score) {
return (a.datetime - b.datetime) > 0 ? -1 : 1;
}
});
// Limit the number of notifications to `maxNotifs`, prioritising unread notifications
if (notifications.read.length + notifications.unread.length > maxNotifs) {
notifications.read.length = maxNotifs - notifications.unread.length;
}
callback(notifications);
});
},
getUnreadCount: function(uid, callback) {
RDB.zcount('uid:' + uid + ':notifications:unread', 0, 10, callback);
},
hasFlag: function(uid, callback) {
RDB.get('uid:1:notifications:flag', function(err, flag) {
if (err) {

View File

@@ -26,19 +26,36 @@ var express = require('express'),
(function(app) {
var templates = null;
app.build_header = function(res) {
return templates['header'].parse({
cssSrc: global.config['theme:src'] || global.nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css',
title: global.config['title'] || 'NodeBB',
csrf:res.locals.csrf_token,
relative_path: global.nconf.get('relative_path')
/**
* `options` object requires: req, res
* accepts: metaTags
*/
app.build_header = function(options, callback) {
var defaultMetaTags = [
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
{ name: 'content-type', content: 'text/html; charset=UTF-8' },
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
{ property: 'og:site_name', content: global.config.title || 'NodeBB' },
],
metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])),
templateValues = {
cssSrc: global.config['theme:src'] || global.nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css',
title: global.config['title'] || 'NodeBB',
csrf: options.res.locals.csrf_token,
relative_path: global.nconf.get('relative_path'),
meta_tags: metaString
};
meta.build_title(options.title, (options.req.user ? options.req.user.uid : 0), function(err, title) {
if (!err) templateValues.browserTitle = title;
callback(null, templates['header'].parse(templateValues));
});
};
// Middlewares
app.use(express.favicon(path.join(__dirname, '../', 'public', 'favicon.ico')));
app.use(require('less-middleware')({ src: path.join(__dirname, '../', 'public') }));
//app.use(express.static(path.join(__dirname, '../', 'public')));
app.use(global.nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public')));
app.use(express.bodyParser()); // Puts POST vars in request.body
app.use(express.cookieParser()); // If you want to parse cookies (res.cookies)
@@ -146,8 +163,10 @@ var express = require('express'),
});
return;
}
res.send(app.build_header(res) + app.create_route(route) + templates['footer']);
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route(route) + templates['footer']);
});
});
}(routes[i]));
}
@@ -155,21 +174,38 @@ var express = require('express'),
app.get('/', function(req, res) {
categories.getAllCategories(function(returnData) {
async.parallel({
"header": function(next) {
app.build_header({
req: req,
res: res,
metaTags: [
{ name: "title", content: global.config.title || 'NodeBB' },
{ name: "description", content: global.config.description || '' },
{ property: 'og:title', content: 'Index | ' + (global.config.title || 'NodeBB') },
{ property: "og:type", content: 'website' }
]
}, next);
},
"categories": function(next) {
categories.getAllCategories(function(returnData) {
next(null, returnData);
}, 0);
}
}, function(err, data) {
res.send(
app.build_header(res) +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/home'].parse(returnData) + '\n\t</noscript>' +
data.header +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/home'].parse(data.categories) + '\n\t</noscript>' +
app.create_route('') +
templates['footer']
);
}, 0);
})
});
app.get('/topic/:topic_id/:slug?', function(req, res) {
var tid = req.params.topic_id;
if (tid.match('.rss')) {
if (tid.match(/^\d+\.rss$/)) {
fs.readFile('feeds/topics/' + tid, function (err, data) {
if (err) {
res.type('text').send(404, "Unable to locate an rss feed at this location.");
@@ -181,14 +217,49 @@ var express = require('express'),
return;
}
async.waterfall([
function(next) {
topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), function(err, topicData) {
next(err, topicData);
});
},
function(topicData, next) {
var posts = topicData.posts.push(topicData.main_posts[0]),
lastMod = 0,
timestamp;
for(var x=0,numPosts=topicData.posts.length;x<numPosts;x++) {
timestamp = parseInt(topicData.posts[x].timestamp, 10);
if (timestamp > lastMod) lastMod = timestamp;
}
var topic_url = tid + (req.params.slug ? '/' + req.params.slug : '');
topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), function(err, topic) {
app.build_header({
req: req,
res: res,
title: topicData.topic_name,
metaTags: [
{ name: "title", content: topicData.topic_name },
{ property: 'og:title', content: topicData.topic_name + ' | ' + (global.config.title || 'NodeBB') },
{ property: "og:type", content: 'article' },
{ property: "og:url", content: global.nconf.get('url') + 'topic/' + topicData.slug },
{ 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:modified_time', content: new Date(lastMod).toISOString() },
{ property: 'article:section', content: topicData.category_name }
]
}, function(err, header) {
next(err, {
header: header,
topics: topicData
});
});
},
], function(err, data) {
if (err) return res.redirect('404');
var topic_url = tid + (req.params.slug ? '/' + req.params.slug : '');
res.send(
app.build_header(res) +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/topic'].parse(topic) + '\n\t</noscript>' +
data.header +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/topic'].parse(data.topics) + '\n\t</noscript>' +
'\n\t<script>templates.ready(function(){ajaxify.go("topic/' + topic_url + '");});</script>' +
templates['footer']
);
@@ -197,7 +268,8 @@ var express = require('express'),
app.get('/category/:category_id/:slug?', function(req, res) {
var cid = req.params.category_id;
if (cid.match('.rss')) {
if (cid.match(/^\d+\.rss$/)) {
fs.readFile('feeds/categories/' + cid, function (err, data) {
if (err) {
res.type('text').send(404, "Unable to locate an rss feed at this location.");
@@ -209,16 +281,36 @@ var express = require('express'),
return;
}
var category_url = cid + (req.params.slug ? '/' + req.params.slug : '');
categories.getCategoryById(cid, 0, function(returnData) {
if(!returnData) {
res.redirect('404');
return;
async.waterfall([
function(next) {
categories.getCategoryById(cid, 0, function(err, categoryData) {
next(err, categoryData);
});
},
function(categoryData, next) {
app.build_header({
req: req,
res: res,
title: categoryData.category_name,
metaTags: [
{ name: 'title', content: categoryData.category_name },
{ name: 'description', content: categoryData.category_description },
{ property: "og:type", content: 'website' }
]
}, function(err, header) {
next(err, {
header: header,
categories: categoryData
});
});
}
], function(err, data) {
if(err) return res.redirect('404');
var category_url = cid + (req.params.slug ? '/' + req.params.slug : '');
res.send(
app.build_header(res) +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/category'].parse(returnData) + '\n\t</noscript>' +
data.header +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/category'].parse(data.categories) + '\n\t</noscript>' +
'\n\t<script>templates.ready(function(){ajaxify.go("category/' + category_url + '");});</script>' +
templates['footer']
);
@@ -226,7 +318,25 @@ var express = require('express'),
});
app.get('/confirm/:code', function(req, res) {
res.send(app.build_header(res) + '<script>templates.ready(function(){ajaxify.go("confirm/' + req.params.code + '");});</script>' + templates['footer']);
app.build_header({ req: req, res: res }, function(header) {
res.send(header + '<script>templates.ready(function(){ajaxify.go("confirm/' + req.params.code + '");});</script>' + templates['footer']);
});
});
app.get('/sitemap.xml', function(req, res) {
var sitemap = require('./sitemap.js');
sitemap.render(function(xml) {
res.type('xml').set('Content-Length', xml.length).send(xml);
});
});
app.get('/robots.txt', function(req, res) {
res.set('Content-Type', 'text/plain');
res.send( "User-agent: *\n" +
"Disallow: \n" +
"Disallow: /admin/\n" +
"Sitemap: " + global.nconf.get('url') + "sitemap.xml");
});
app.get('/api/:method', api_method);
@@ -236,7 +346,7 @@ var express = require('express'),
app.get('/api/:method/:id*', api_method);
app.get('/cid/:cid', function(req, res) {
categories.getCategoryData(req.params.cid, function(data){
categories.getCategoryData(req.params.cid, function(err, data) {
if(data)
res.send(data);
else
@@ -262,20 +372,20 @@ var express = require('express'),
});
});
app.get('/test', function(req, res) {
/*user.get_userslugs_by_uids([1,2], function(data) {
res.send(data);
});*/
var gravatar= require('gravatar');
var img = gravatar.url('', {}, https=false);
res.send(img);
// 'http://www.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e'
/* categories.getCategoryById(1,1, function(data) {
res.send(data);
},1);*/
app.get('/outgoing', function(req, res) {
var url = req.url.split('?');
if (url[1]) {
app.build_header({ req: req, res: res }, function(header) {
res.send(header + templates['outgoing'].parse({
url: url[1],
home: global.nconf.get('url')
}) + templates['footer']);
});
} else {
res.status(404);
res.redirect(global.nconf.get('relative_path') + '/404');
}
});
});
@@ -366,8 +476,9 @@ var express = require('express'),
});
break;
case 'category' :
categories.getCategoryById(req.params.id, uid, function(data) {
res.json(data);
categories.getCategoryById(req.params.id, uid, function(err, data) {
if (!err) res.json(data);
else res.send(404);
}, req.params.id, uid);
break;
case 'recent' :
@@ -402,16 +513,24 @@ var express = require('express'),
}
});
break;
case 'outgoing' :
var url = req.url.split('?');
if (url[1]) {
res.json({
url: url[1],
home: global.nconf.get('url')
});
} else {
res.status(404);
res.redirect(global.nconf.get('relative_path') + '/404');
}
break;
default :
res.json(404, { error: 'unrecognized API endpoint' });
break;
}
}
}(WebServer));
server.listen(nconf.get('port'));

View File

@@ -1,7 +1,7 @@
var SocketIO = require('socket.io').listen(global.server, { log:false }),
cookie = require('cookie'),
connect = require('connect'),
express = require('express'),
user = require('./user.js'),
posts = require('./posts.js'),
favourites = require('./favourites.js'),
@@ -14,6 +14,14 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
postTools = require('./postTools.js'),
meta = require('./meta.js'),
async = require('async'),
RedisStoreLib = require('connect-redis')(express),
redis = require('redis'),
redisServer = redis.createClient(global.nconf.get('redis:port'), global.nconf.get('redis:host')),
RedisStore = new RedisStoreLib({
client: redisServer,
ttl: 60*60*24*14
}),
socketCookieParser = express.cookieParser(global.nconf.get('secret')),
admin = {
'categories': require('./admin/categories.js'),
'user': require('./admin/user.js')
@@ -26,75 +34,56 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
global.io = io;
// Adapted from http://howtonode.org/socket-io-auth
io.set('authorization', function(handshakeData, accept) {
if (handshakeData.headers.cookie) {
handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
handshakeData.sessionID = connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], global.nconf.get('secret'));
if (handshakeData.cookie['express.sid'] == handshakeData.sessionID) {
return accept('Cookie is invalid.', false);
}
} else {
// No cookie sent
return accept('No cookie transmitted', false);
}
// Otherwise, continue unimpeded.
var sessionID = handshakeData.sessionID;
user.get_uid_by_session(sessionID, function(userId) {
if (userId)
users[sessionID] = userId;
else
users[sessionID] = 0;
accept(null, true);
});
});
io.sockets.on('connection', function(socket) {
var hs = socket.handshake;
var hs = socket.handshake,
sessionID, uid;
var uid = users[hs.sessionID];
// if (uid > 0) {
userSockets[uid] = userSockets[uid] || [];
userSockets[uid].push(socket);
user.go_online(uid);
socket.join('uid_' + uid);
// }
/*process.on('uncaughtException', function(err) {
// handle the error safely
console.log("error message "+err);
socket.emit('event:consolelog',{type:'uncaughtException', stack:err.stack, error:err.toString()});
});*/
// Validate the session, if present
socketCookieParser(hs, {}, function(err) {
sessionID = socket.handshake.signedCookies["express.sid"];
RedisStore.get(sessionID, function(err, sessionData) {
if (!err && sessionData && sessionData.passport && sessionData.passport.user) uid = users[sessionID] = sessionData.passport.user;
else uid = users[sessionID] = 0;
socket.emit('event:connect', {status: 1});
userSockets[uid] = userSockets[uid] || [];
userSockets[uid].push(socket);
if(uid) {
socket.join('uid_' + uid);
io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid));
user.getUserField(uid, 'username', function(username) {
socket.emit('event:connect', {status: 1, username:username});
});
}
});
});
socket.on('disconnect', function() {
// if (uid > 0) {
user.go_offline(uid);
delete users[hs.sessionID];
var index = userSockets[uid].indexOf(socket);
if(index !== -1) {
userSockets[uid].splice(index, 1);
}
for(var roomName in rooms) {
var index = userSockets[uid].indexOf(socket);
if(index !== -1) {
userSockets[uid].splice(index, 1);
}
socket.leave(roomName);
if(userSockets[uid].length === 0) {
delete users[sessionID];
if(uid)
io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid));
}
for(var roomName in rooms) {
if(rooms[roomName][hs.sessionID]) {
delete rooms[roomName][hs.sessionID];
}
updateRoomBrowsingText(roomName);
}
socket.leave(roomName);
if(rooms[roomName][socket.id]) {
delete rooms[roomName][socket.id];
}
// }
updateRoomBrowsingText(roomName);
}
});
socket.on('api:get_all_rooms', function(data) {
@@ -105,9 +94,9 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
function getUidsInRoom(room) {
var uids = [];
for(var sessionId in room) {
if(uids.indexOf(room[sessionId]) === -1)
uids.push(room[sessionId]);
for(var socketId in room) {
if(uids.indexOf(room[socketId]) === -1)
uids.push(room[socketId]);
}
return uids;
}
@@ -118,7 +107,8 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
for(var i=0; i<clients.length; ++i) {
var hs = clients[i].handshake;
if(hs && !users[hs.sessionID]) {
if(hs && !users[sessionID]) {
++anonCount;
}
}
@@ -163,17 +153,17 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
rooms[data.enter] = rooms[data.enter] || {};
if(data.leave) {
if (uid) {
rooms[data.enter][hs.sessionID] = uid;
if (uid) {
rooms[data.enter][socket.id] = uid;
if (data.leave && rooms[data.leave] && rooms[data.leave][hs.sessionID]) {
delete rooms[data.leave][hs.sessionID];
}
if (data.leave && rooms[data.leave] && rooms[data.leave][socket.id]) {
delete rooms[data.leave][socket.id];
}
updateRoomBrowsingText(data.leave);
}
if(data.leave)
updateRoomBrowsingText(data.leave);
updateRoomBrowsingText(data.enter);
if (data.enter != 'admin')
@@ -185,7 +175,6 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:updateHeader', function(data) {
if(uid) {
user.getUserFields(uid, data.fields, function(fields) {
fields.uid = uid;
socket.emit('api:updateHeader', fields);
@@ -236,8 +225,25 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
user.reset.commit(socket, data.code, data.password);
});
function isUserOnline(uid) {
return !!userSockets[uid] && userSockets[uid].length > 0;
}
socket.on('api:user.get_online_users', function(data) {
user.get_online_users(socket, data);
var returnData = [];
for(var i=0; i<data.length; ++i) {
var uid = data[i];
if(isUserOnline(uid))
returnData.push(uid);
else
returnData.push(0);
}
socket.emit('api:user.get_online_users', returnData);
});
socket.on('api:user.isOnline', function(uid) {
socket.emit('api:user.isOnline', isUserOnline(uid));
});
socket.on('api:user.changePassword', function(data) {
@@ -280,43 +286,60 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:user.follow', function(data) {
user.follow(uid, data.uid, function(success) {
if(success) {
user.getUserField(data.uid, 'username', function(username) {
socket.emit('event:alert', {
title: 'Following',
message: 'You are now following ' + username + '!',
type: 'success',
timeout: 2000
});
});
}
});
if(uid) {
user.follow(uid, data.uid, function(success) {
if(success) {
user.getUserField(data.uid, 'username', function(username) {
socket.emit('event:alert', {
title: 'Following',
message: 'You are now following ' + username + '!',
type: 'success',
timeout: 2000
});
});
}
});
}
});
socket.on('api:user.unfollow', function(data) {
user.unfollow(uid, data.uid, function(success) {
if(success) {
user.getUserField(data.uid, 'username', function(username) {
socket.emit('event:alert', {
title: 'Unfollowed',
message: 'You are no longer following ' + username + '!',
type: 'success',
timeout: 2000
});
});
}
});
if(uid) {
user.unfollow(uid, data.uid, function(success) {
if(success) {
user.getUserField(data.uid, 'username', function(username) {
socket.emit('event:alert', {
title: 'Unfollowed',
message: 'You are no longer following ' + username + '!',
type: 'success',
timeout: 2000
});
});
}
});
}
});
socket.on('api:user.saveSettings', function(data) {
if(uid) {
user.setUserFields(uid, {
showemail:data.showemail
});
socket.emit('event:alert', {
title: 'Saved',
message: 'Settings saved!',
type: 'success',
timeout: 2000
});
}
});
socket.on('api:topics.post', function(data) {
topics.post(socket, uid, data.title, data.content, data.category_id);
topics.post(socket, uid, data.title, data.content, data.category_id, data.images);
});
socket.on('api:posts.reply', function(data) {
posts.reply(socket, data.topic_id, uid, data.content);
posts.reply(socket, data.topic_id, uid, data.content, data.images);
});
socket.on('api:user.active.get', function() {
@@ -383,6 +406,13 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
});
socket.on('api:posts.edit', function(data) {
if(!data.title || data.title.length < topics.minimumTitleLength) {
topics.emitTitleTooShortAlert(socket);
return;
} else if (!data.content || data.content.length < posts.minimumPostLength) {
posts.emitContentTooShortAlert(socket);
return;
}
postTools.edit(uid, data.pid, data.title, data.content);
});
@@ -421,6 +451,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
});
socket.on('sendChatMessage', function(data) {
var touid = data.touid;
if(userSockets[touid]) {
@@ -433,7 +464,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
for(var x=0;x<numSockets;x++) {
userSockets[touid][x].emit('chatMessage', {fromuid:uid, username:username, message:finalMessage});
}
notifications.create(finalMessage, 5, '#', 'notification_'+uid+'_'+touid, function(nid) {
notifications.push(nid, [touid], function(success) {
@@ -594,6 +625,12 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
else
socket.emit('api:admin.user.search', null);
});
socket.on('api:admin:themes.getInstalled', function() {
meta.themes.get(function(err, themeArr) {
socket.emit('api:admin:themes.getInstalled', themeArr);
});
});
});
}(SocketIO));