Compare commits

...

199 Commits

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

also fixes julianlam/nodebb-plugin-mentions#1
2013-08-22 14:40:28 -04:00
Baris Soner Usakli
4cdb7ff32b closes #192 2013-08-22 13:47:07 -04:00
Julian Lam
7cbb01a78a making RDB available to plugins without require 2013-08-22 11:42:49 -04:00
Julian Lam
555eddff83 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-22 10:48:28 -04:00
Julian Lam
a3cab53b73 added username mentions plugin to default, and tweaked admin panel to show
plugins installed via npm
2013-08-22 10:47:24 -04:00
Baris Soner Usakli
454d5827fd overflow hidden on users recent posts 2013-08-22 10:18:17 -04:00
Baris Soner Usakli
186c426691 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-22 10:14:15 -04:00
Baris Soner Usakli
5e1e1ecf6f moved refreshTitle into load_template callback 2013-08-22 10:14:07 -04:00
Julian Lam
2d3d0f688a a couple minor 'tweaks' to the plugin system so that it works with npm installed plugins 2013-08-22 09:50:29 -04:00
Julian Lam
7975844e4d renamed two hooks and added filter:post.get hook 2013-08-21 22:40:47 -04:00
Baris Soner Usakli
f2b138510e removed console.log 2013-08-21 19:58:45 -04:00
Baris Soner Usakli
1c95ef4060 fix for infinite scroll crashing, issue #187 2013-08-21 19:57:11 -04:00
Baris Soner Usakli
b43e12a42a closes #187, closes #190 2013-08-21 19:21:49 -04:00
Baris Soner Usakli
3f793ae902 changed document.body.offsetHeight to .height() 2013-08-21 14:33:05 -04:00
Baris Soner Usakli
ac56f3a30a change document.body.scrollTop to .scrollTop() 2013-08-21 13:17:16 -04:00
psychobunny
e2ffac74bc added css classes to hide/show elements based on logged in status; hid unread category to anonymous users
added some general classes .nodebb-loggedin and .nodebb-loggedout for
toggling display based on user status
2013-08-22 00:40:37 +08:00
psychobunny
1c08ca54c5 added subtle glowing css animation to unread notification icon 2013-08-22 00:22:05 +08:00
psychobunny
f1547a7b1f added badge to header for unread topics count 2013-08-22 00:08:11 +08:00
psychobunny
746fa93c80 added API call for total unread topics; moved unread notification parsing to client side
also fixed a bug where the new notification icon glow would disappear on
page refresh even if there were existing notifications.
2013-08-21 23:34:35 +08:00
psychobunny
5ab1758d28 simple toaster popup on new notification 2013-08-21 23:08:51 +08:00
Julian Lam
2e4e1df3d9 closed #186 - infinite scroller not working in firefox 2013-08-20 22:36:46 -04:00
psychobunny
eded61d66e typo 2013-08-21 03:40:23 +08:00
psychobunny
d9360da9a5 default motd: removed version number and button text on mobile layouts 2013-08-21 03:37:48 +08:00
Baris Usakli
2b7a1b7515 changed the recursion to load posts to a while loop 2013-08-20 12:31:08 -04:00
Baris Usakli
1e66116e1d closes #181 2013-08-20 12:11:17 -04:00
Baris Soner Usakli
a95582b382 closes #183, closes #182 2013-08-20 00:50:59 -04:00
Baris Soner Usakli
7860cfdec3 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-20 00:26:58 -04:00
Baris Soner Usakli
30bbea9c74 closes #175 2013-08-20 00:26:50 -04:00
Baris Usakli
481105d6be reverted that back, it would take other users to the topic too 2013-08-19 14:54:11 -04:00
Baris Usakli
f939a632a6 take to topic after creation 2013-08-19 14:43:37 -04:00
Baris Usakli
c05f56d28c fix for category view when its empty 2013-08-19 14:28:51 -04:00
Baris Usakli
b844251587 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-19 14:03:31 -04:00
Baris Usakli
b9bd907a6b speech bubbles 2013-08-19 14:03:20 -04:00
psychobunny
17d86a2a35 force vertical scrollbar to prevent centering jumps during navigation 2013-08-20 01:50:19 +08:00
Baris Usakli
c70c67394a closes #171 2013-08-19 13:31:04 -04:00
Baris Usakli
92d3559146 closes #176 2013-08-19 11:56:40 -04:00
Baris Usakli
0e9a3c3a9f hide load more button if not topics are left 2013-08-19 11:02:31 -04:00
Baris Usakli
41263f0332 closes #178 2013-08-19 10:58:02 -04:00
Julian Lam
3747427538 updated readme to remove indiegogo info, and to add 0.0.5 screenshots 2013-08-19 10:13:18 -04:00
Baris Soner Usakli
b65554ca15 removed console.log 2013-08-16 17:04:41 -04:00
Baris Soner Usakli
00cb15d3c8 check if there is follow element 2013-08-16 17:04:08 -04:00
Baris Soner Usakli
6690f49c4e added profile views to users, little cleanup to use app.addCommasToNumbers 2013-08-16 13:03:56 -04:00
Baris Soner Usakli
ff805a704d closes #175 2013-08-15 17:03:43 -04:00
Baris Soner Usakli
f83be710a0 closes #172, #173, #174 2013-08-15 16:50:00 -04:00
Julian Lam
3933549659 committing fixes to composer window 2013-08-15 14:04:12 -04:00
Julian Lam
4993b74c23 Merge remote-tracking branch 'origin' 2013-08-15 11:40:53 -04:00
Julian Lam
76e7a98c88 style fixes to image-drop list in composer window 2013-08-15 11:40:41 -04:00
Baris Soner Usakli
999e98e43d add label to banned users, dont hide their posts 2013-08-14 17:42:36 -04:00
Baris Usakli
74af205426 banned users cant login, show error messages on failed logins 2013-08-14 15:49:56 -04:00
Baris Usakli
9ad82f4907 dont filter banned when searching if user is admin 2013-08-14 14:26:09 -04:00
Baris Usakli
2e6b37e018 wrapped search form in li 2013-08-14 13:56:06 -04:00
Baris Usakli
67070e335e removed console.log 2013-08-14 13:44:18 -04:00
Baris Usakli
22536e694c fixes wrong topics getting loaded into wrong categories 2013-08-14 13:43:42 -04:00
Baris Usakli
929282a2f7 ban users, closes #125, banning a user hides all posts topics of a user and their profile page becomes inaccessible 2013-08-14 13:32:07 -04:00
Baris Soner Usakli
b0092b68c6 closes #166 2013-08-13 17:29:16 -04:00
Baris Usakli
91446378bd require winston 2013-08-13 16:02:21 -04:00
Baris Usakli
dceec0ce46 more winston, issue #62 2013-08-13 16:00:24 -04:00
Baris Usakli
1856e394f3 more winston 2013-08-13 15:25:40 -04:00
Baris Usakli
8dc7a0dbbf changed favouriting to wait for socket call to end before changing star class 2013-08-13 15:05:35 -04:00
Baris Usakli
6e17ff7981 added winston, added wrapper for winston.error until they fix it, issue #62 2013-08-13 14:45:28 -04:00
Julian Lam
1d81f43249 0.0.5 2013-08-13 14:10:22 -04:00
Julian Lam
645d0f60e4 dropping back to 0.0.4 for republish of 0.0.5 2013-08-13 14:09:43 -04:00
Julian Lam
e1d7fed1b6 Merge pull request #167 from miduga/my-commits
Typo with ending bracket
2013-08-13 11:09:09 -07:00
Miguel Ángel Durán García
deff75ad06 Typo with ending bracket 2013-08-13 19:37:01 +02:00
Julian Lam
85fb03c0f5 0.0.5 2013-08-13 13:07:19 -04:00
Julian Lam
74445c3d30 temporarily dropping back to 0.0.4 for re-issue 2013-08-13 13:06:59 -04:00
Baris Usakli
ca3629d89b forgot to commit oops 2013-08-13 13:05:15 -04:00
Baris Usakli
16a95dd408 fixed welcome back message, it wasnt showing up in some cases 2013-08-13 12:58:07 -04:00
Julian Lam
b96084fb16 0.0.5 2013-08-13 12:14:11 -04:00
Julian Lam
c3c5f5ab92 closed #165 - issue where the site title was "undefined" if no title was set in the config, parenthesis issue 2013-08-13 12:10:07 -04:00
Baris Usakli
0fef0aa582 disable load more users button if there is not more users, dont show mark all read in unread if user isnt logged in 2013-08-13 11:39:19 -04:00
Baris Usakli
1894d0a469 moved confings into redis 2013-08-13 11:25:10 -04:00
Baris Usakli
259ba9b9b5 added post settings to admin page 2013-08-13 10:51:25 -04:00
Baris Soner Usakli
46afbc0ba0 assorted config fixes 2013-08-12 19:00:31 -04:00
Baris Soner Usakli
dd3b01dfed closes #144 2013-08-12 14:49:49 -04:00
Baris Soner Usakli
48bb1b06c2 no need to store container 2013-08-12 14:35:28 -04:00
Baris Soner Usakli
9fb63d45c1 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-12 14:33:02 -04:00
Baris Soner Usakli
ded0200355 upgraded userlist to 3 sorted sets, run node app -upgrade after updating to this commit, added infinite scrolling to all user pages, added ops per second to redis page, changed the percentage to 90% for infinite scrolling to kick in 2013-08-12 14:32:56 -04:00
Julian Lam
64b166ca8c Merge pull request #162 from miduga/my-commits
#161 - meta.js problem with empty config var
2013-08-12 09:17:32 -07:00
Miguel Ángel Durán García
33994ed638 #161 - meta.js problem with empty config var
If config is empty, give to it an empty object value.
2013-08-12 18:06:56 +02:00
Julian Lam
4457228903 closed #134 2013-08-11 16:41:49 -04:00
Julian Lam
f4573d23b5 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-11 16:13:04 -04:00
Julian Lam
e8f1d645fe closed #160
new build_title function to be called via socket

also added categories.getCategoryField and refactored all calls to
getTopicField to be error-first
2013-08-11 16:12:20 -04:00
Baris Soner Usakli
149e8ac78e Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-11 14:50:14 -04:00
Baris Soner Usakli
24b951ab5b added api route for 403 2013-08-11 14:50:12 -04:00
Julian Lam
04d94a7462 turns out it was working as intended(tm)
closed #158
2013-08-11 13:27:05 -04:00
Julian Lam
9bf8739841 removed debug comments 2013-08-11 13:23:41 -04:00
Julian Lam
26944ae686 addressed issue #158, where topic buttons in admin panel were not working
correctly
2013-08-11 13:22:48 -04:00
Julian Lam
dd6c9e03cb added message to admin panel if no themes are found 2013-08-11 13:13:44 -04:00
Julian Lam
45c10d75f3 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-11 13:10:11 -04:00
Julian Lam
272806a25d closed #159 - issue where theme listing would hang if a theme folder did
not contain theme.json

Also refactored theme socket event a bit
2013-08-11 13:09:27 -04:00
Julian Lam
27f874759e added message about using "forever" module to run production version of
nodebb
2013-08-11 12:45:44 -04:00
Baris Soner Usakli
e1d6b77eb1 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-10 16:20:28 -04:00
Baris Soner Usakli
0a7d41dafa updated reds to 0.2.4 2013-08-10 16:20:16 -04:00
Julian Lam
644fb375db Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-10 16:16:05 -04:00
Julian Lam
81f94c56e0 using hgetall instead of hkeys+asynchget, simplifying meta.js, closes #157 2013-08-10 16:15:58 -04:00
Baris Soner Usakli
50c34e4f33 added infinite scrolling to unread page, issue #141 2013-08-10 16:14:50 -04:00
Baris Soner Usakli
cbd34a4411 removed console.log 2013-08-09 20:45:12 -04:00
Baris Soner Usakli
244801a0bf added infinite loading to recent page, #141 2013-08-09 20:44:08 -04:00
Baris Soner Usakli
1cc8214d16 infinite scrolling for topics in category view, changed how topics are stored in categories, using sorted sets instead of sets now, if you update to this commit run node app --upgrade to upgrade the redis schema, #141 2013-08-09 20:03:19 -04:00
Baris Soner Usakli
04e0d075ec added upgrade file and nconf check 2013-08-09 15:17:50 -04:00
Julian Lam
917468d676 Merge pull request #156 from miduga/master
Remove unneded type tags for scripts
2013-08-09 07:34:42 -07:00
Miguel Ángel Durán García
1afc4f39a8 Remove unneded type tags for scripts
In HTML5 we can omit the type tag for scripts because it's useless in
javascript.

http://www.w3.org/html/wg/drafts/html/master/scripting-1.html#the-script
-element

"When used to include dynamic scripts, the scripts may either be
embedded inline or may be imported from an external file using the src
attribute. If the language is not that described by "text/javascript",
then the type attribute must be present, as described below. Whatever
language is used, the contents of the script element must conform with
the requirements of that language's specification."
2013-08-09 16:15:04 +02:00
Baris Usakli
02e707c3b5 removed console.log 2013-08-08 16:44:25 -04:00
Baris Usakli
34ce17e522 removed eventlisteners for scroll 2013-08-08 16:43:46 -04:00
Baris Usakli
a100eb1bb2 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-08 15:54:48 -04:00
Baris Usakli
b6281f020b removed console.logs 2013-08-08 15:54:39 -04:00
Julian Lam
78c1139ad9 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-08 15:05:00 -04:00
Julian Lam
08a45e40a2 fixed outgoing route to handle links with querystrings in it (using req.query instead of req.url). closes #154. 2013-08-08 15:04:22 -04:00
Baris Usakli
e979d1bf0d fixed home loading 2013-08-08 14:30:42 -04:00
Baris Usakli
e69928d624 strip tags in recent replies 2013-08-08 14:12:28 -04:00
Baris Usakli
8bc60be7c3 dont infinite load if there are no topics loaded 2013-08-08 14:00:40 -04:00
Baris Usakli
99ac782875 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-08 13:04:36 -04:00
Baris Usakli
c1c9d24ee5 deleted posts are collapsed clicking on them expands them, closes #138 2013-08-08 13:04:26 -04:00
Julian Lam
e2d0ca9669 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-08 13:04:11 -04:00
Julian Lam
661596e4cc updated reds package in package.json to accept any version in the 0.2.x branch 2013-08-08 13:03:54 -04:00
Baris Usakli
ac3fe1fc6f closes #93 2013-08-08 12:49:01 -04:00
Baris Usakli
0bd56196cf index topic titles too 2013-08-08 11:40:31 -04:00
Julian Lam
7251af56e3 refactored notifications library to mark all notifs read when the menu is
opened (closes #134)
2013-08-07 22:42:34 -04:00
Julian Lam
984ac21e52 updating notifications lib to not expose every method 2013-08-07 22:07:35 -04:00
Julian Lam
cb1258da9c removing aptana config file from repo 2013-08-07 21:42:39 -04:00
Baris Soner Usakli
f0759863e6 reindex all call 2013-08-06 15:46:15 -04:00
Baris Soner Usakli
0561710016 highlight search results 2013-08-04 14:48:36 -04:00
Baris Soner Usakli
8423684dee closes #145, fixed 404 not rendering 2013-08-04 14:08:05 -04:00
Baris Soner Usakli
6ff0c55dae swaped notif and search box 2013-08-03 20:57:03 -04:00
Baris Soner Usakli
d6b9a2799b full text search using reds, issue #142 2013-08-03 20:54:16 -04:00
Baris Soner Usakli
797d05a84a removed the other api_method routes 2013-08-02 14:29:40 -04:00
Baris Soner Usakli
a5acf31fc6 posts on user account page scroll to the post now 2013-08-01 18:00:32 -04:00
Baris Soner Usakli
23fcdde25e removed console.log and commented out code 2013-08-01 17:36:01 -04:00
Baris Soner Usakli
7297a26685 scroll to post 2 2013-08-01 17:27:37 -04:00
Baris Usakli
720dd9e960 scroll to posts 2013-08-01 16:11:00 -04:00
Baris Usakli
dbfd3c19a1 removed api_method route from user.js 2013-08-01 13:50:57 -04:00
Baris Soner Usakli
f20b7b5113 closes #133 2013-07-31 16:10:39 -04:00
Baris Soner Usakli
9f2838849e moved the imgur clientID to the server config file 2013-07-31 15:33:36 -04:00
Baris Soner Usakli
59381ee1d3 cleaned up webserver.js a bit, moved api routes to its own file, closes #54 2013-07-31 15:17:03 -04:00
Baris Soner Usakli
6fbecd00b0 allow - and space in username 2013-07-31 13:00:32 -04:00
Baris Soner Usakli
a0a1b14e2e only allow alphanumberic charactesr in username 2013-07-31 12:48:30 -04:00
Baris Soner Usakli
f4fdabd3ca changed to wrapper alert functions 2013-07-30 18:34:51 -04:00
Baris Soner Usakli
3f6f4e347d change some socket calls to use the callback, when user changes their email if they are using a gravatar picture it will be updated too, fixed email updating 2013-07-30 18:30:43 -04:00
Julian Lam
235553eaf6 Merge branch 'plugins'
Conflicts:
	src/posts.js
2013-07-29 16:01:00 -04:00
Julian Lam
1bee527f41 added note linking to wiki page for authoring plugins... also showing something if no plugins are installed 2013-07-29 15:57:14 -04:00
Julian Lam
3578ce1925 admin panel integration completion + styling of admin panel plugins page 2013-07-29 15:45:13 -04:00
Julian Lam
c2d7bf87e9 Merge branch 'plugins' of github.com:designcreateplay/NodeBB into plugins 2013-07-29 15:16:21 -04:00
Julian Lam
777173d9f4 adding admin panel integration to plugins (issue #143) 2013-07-29 15:15:49 -04:00
Julian Lam
0a3a970b52 fixed issue where if a hook had no attached methods, it wouldn't work at all... or put simply... nodebb'd break if you had no plugins, heh. 2013-07-29 15:14:41 -04:00
Baris Soner Usakli
b46d81e3e4 removed console.log 2013-07-29 13:37:16 -04:00
Baris Soner Usakli
3273c0558b anon users always see topics as unread 2013-07-29 13:33:54 -04:00
Baris Soner Usakli
6b2be772e7 removed test route 2013-07-28 18:31:20 -04:00
Baris Soner Usakli
78f462e1e6 added a new section that only shows unread topics, added mark all read button, closes #140 2013-07-28 18:27:49 -04:00
Julian Lam
871591b92d sanity check if a plugin's method was not found on hook call 2013-07-28 14:24:34 -04:00
Baris Soner Usakli
811d2df728 moved getTopicsByTids and getLatestTopics to topics.js 2013-07-28 14:02:50 -04:00
Julian Lam
307f6f34b9 added plugins.showInstalled method 2013-07-28 12:52:58 -04:00
Julian Lam
2d64c9979e sending pid as well as content in action:save_post_content hook 2013-07-28 03:01:58 -04:00
Julian Lam
34bb9dddb5 sticking to style guide for method names (camelcase) 2013-07-28 02:24:41 -04:00
Julian Lam
25a1d25108 tested filter-type hooks, working! :) 2013-07-28 01:37:40 -04:00
Julian Lam
ec4ab19a1e first pass at plugin system -- not tested! 2013-07-28 01:16:21 -04:00
Julian Lam
2e83ad448d added new plugins folder to repo 2013-07-27 21:45:51 -04:00
Baris Soner Usakli
b35ecd08ea use hasReadTopic 2013-07-27 13:59:14 -04:00
Baris Soner Usakli
d32b130d40 added function for marking all topics read #140 2013-07-27 13:05:03 -04:00
Baris Soner Usakli
cca83b9c5c bleh 2013-07-27 12:55:33 -04:00
Baris Soner Usakli
57a4741e9e show message if there are not recent topics 2013-07-26 15:49:03 -04:00
Baris Soner Usakli
23598361b8 closes #139 2013-07-26 15:04:18 -04:00
Baris Soner Usakli
3f8b3894d8 C:/Program Files (x86)/Git/recent shows only the last 24 hours 2013-07-26 14:48:16 -04:00
Julian Lam
557c6effdd updated screenshots for v0.0.4 2013-07-25 21:28:27 -04:00
Baris Soner Usakli
916b46618c another fix 2013-07-25 18:06:20 -04:00
Baris Usakli
2647666de6 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-07-25 17:02:06 -04:00
Baris Usakli
8cc3859e72 fixed for email visibility icon if data isnt set 2013-07-25 17:01:55 -04:00
87 changed files with 4177 additions and 2071 deletions

2
.gitignore vendored
View File

@@ -11,3 +11,5 @@ public/css/*.css
public/themes/*
*.sublime-project
*.sublime-workspace
plugins/*
.project

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>node-forum</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
<nature>com.aptana.projects.webnature</nature>
</natures>
</projectDescription>

View File

@@ -1,16 +1,15 @@
Please support NodeBB development! Check out our IndieGoGo campaign and like, share, and follow us :)
[NodeBB Homepage](http://www.nodebb.org/ "NodeBB") # [IndieGoGo campaign](https://www.indiegogo.com/projects/nodebb-the-discussion-platform-of-the-future/ "IndieGoGo") # [Follow on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") # [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
[NodeBB Homepage](http://www.nodebb.org/ "NodeBB") # [Follow on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter") # [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
# NodeBB
**NodeBB** is a robust Node.js driven forum built on a redis database. It is powered by web sockets, and is compatible down to IE8.
![NodeBB Main Category Listing](http://i.imgur.com/u2zamT4.png)
![NodeBB Main Category Listing](http://imgur.com/BdImzGs.png)
![NodeBB Topic Page](http://i.imgur.com/KrvSoXV.png)
![NodeBB Topic Page](http://imgur.com/Lk7E3KR.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 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.

87
app.js
View File

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

4
nodebb
View File

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

View File

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

0
plugins/.gitignore vendored Normal file
View File

View File

@@ -6,15 +6,6 @@
margin-bottom: 10px;
padding: 10px;
cursor: move;
width: 800px;
}
.entry-row {
border-radius: 10px;
margin-bottom: 10px;
padding: 10px;
cursor: move;
width: 695px;
}
input.description {
@@ -94,6 +85,10 @@
&:hover {
background-color: rgba(128, 128, 128, 0.2);
}
&.no-themes {
font-style: italic;
}
}
}
@@ -141,4 +136,22 @@
}
}
}
.plugins {
li {
list-style-type: none;
background: rgba(64, 64, 64, 0.05);
padding: 1em;
border-left: 5px solid #08c;
h2 {
font-size: 16px;
margin: 0;
}
p {
font-size: 12px;
}
}
}
}

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ var ajaxify = {};
(function($) {
var location = document.location || window.location,
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
content = null;
@@ -26,29 +26,31 @@ var ajaxify = {};
};
ajaxify.go = function(url, callback, template, quiet) {
$(window).off('scroll');
// leave room and join global
app.enter_room('global');
app.showLoginMessage();
var url = url.replace(/\/$/, "");
var hash = window.location.hash;
if(url.indexOf(RELATIVE_PATH.slice(1)) !== -1) {
url = url.slice(RELATIVE_PATH.length);
}
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].split('?')[0];
}
} else if (templates[url]) {
tpl_url = url;
}
if (templates.is_available(tpl_url) && !templates.force_refresh(tpl_url)) {
if (quiet !== true) {
window.history.pushState({
@@ -65,9 +67,18 @@ var ajaxify = {};
if (callback) {
callback();
}
app.process_page();
jQuery('#content, #footer').stop(true, true).fadeIn(200);
jQuery('#content, #footer').stop(true, true).fadeIn(200, function() {
if(window.location.hash)
hash = window.location.hash;
if(hash)
app.scrollToPost(hash.substr(1));
});
utils.refreshTitle(url);
}, url, template);
return true;
@@ -83,9 +94,15 @@ var ajaxify = {};
// Enhancing all anchors to ajaxify...
$(document.body).on('click', 'a', function(e) {
if (this.href == window.location.href + "#") return;
function hrefEmpty(href) {
return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#";
}
if(hrefEmpty(this.href)) return;
var url = this.href.replace(rootUrl +'/', '');
if (this.target !== '') return;
if (!e.ctrlKey && e.which === 1) {
@@ -112,7 +129,7 @@ var ajaxify = {};
script.type = "text/javascript";
try {
script.appendChild(document.createTextNode(data));
script.appendChild(document.createTextNode(data));
} catch(e) {
script.text = data;
}
@@ -148,5 +165,5 @@ var ajaxify = {};
evalScript(scripts[i]);
}
};
}(jQuery));

View File

@@ -5,33 +5,31 @@ var socket,
(function() {
var showWelcomeMessage = false;
function loadConfig() {
$.ajax({
url: RELATIVE_PATH + '/config.json?v=' + new Date().getTime(),
url: RELATIVE_PATH + '/api/config',
success: function(data) {
API_URL = data.api_url;
config = data;
socket = io.connect(config.socket.address + (config.socket.port ? ':' + config.socket.port : ''));
var reconnecting = false;
var reconnectTries = 0;
socket.on('event:connect', function(data) {
console.log('connected to nodebb socket: ', data);
app.username = data.username;
app.showLoginMessage();
});
socket.on('event:alert', function(data) {
app.alert(data);
});
socket.on('event:consolelog', function(data) {
console.log(data);
});
socket.on('connect', function(data){
if(reconnecting) {
setTimeout(function(){
@@ -48,28 +46,28 @@ var socket,
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
}
});
socket.on('reconnecting', function(data) {
function showDisconnectModal() {
$('#disconnect-modal').modal({
backdrop:'static',
show:true
});
$('#reload-button').on('click',function(){
$('#disconnect-modal').modal('hide');
window.location.reload();
});
}
reconnecting = true;
reconnectTries++;
if(reconnectTries > 4) {
showDisconnectModal();
return;
}
app.alert({
alert_id: 'connection_alert',
title: 'Reconnecting',
@@ -78,15 +76,15 @@ var socket,
timeout: 5000
});
});
socket.on('api:user.get_online_users', function(users) {
jQuery('.username-field').each(function() {
if (this.processed === true)
if (this.processed === true)
return;
var el = jQuery(this),
uid = el.parents('li').attr('data-uid');
if (uid && jQuery.inArray(uid, users) !== -1) {
el.find('i').remove();
el.prepend('<i class="icon-circle"></i>');
@@ -94,19 +92,19 @@ var socket,
el.find('i').remove();
el.prepend('<i class="icon-circle-blank"></i>');
}
el.processed = true;
});
});
app.enter_room('global');
},
async: false
});
}
// takes a string like 1000 and returns 1,000
app.addCommas = function(text) {
return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
@@ -123,14 +121,14 @@ var socket,
});
}
// use unique alert_id to have multiple alerts visible at a time, use the same alert_id to fade out the current instance
// use unique alert_id to have multiple alerts visible at a time, use the same alert_id to fade out the current instance
// type : error, success, info, warning/notify
// title = bolded title text
// message = alert message content
// timeout default = permanent
// location : alert_window (default) or content
app.alert = function(params) {
var alert_id = 'alert_button_' + ((params.alert_id) ? params.alert_id : new Date().getTime());
var alert_id = 'alert_button_' + ((params.alert_id) ? params.alert_id : new Date().getTime());
var alert = $('#'+alert_id);
@@ -140,7 +138,7 @@ var socket,
$(this).remove();
});
}, timeout);
$(div).attr('timeoutId', timeoutId);
}
@@ -148,7 +146,7 @@ var socket,
alert.find('strong').html(params.title);
alert.find('p').html(params.message);
alert.attr('class', "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type));
clearTimeout(alert.attr('timeoutId'));
startTimeout(alert, params.timeout);
}
@@ -162,7 +160,7 @@ var socket,
strong.innerHTML = params.title;
div.className = "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type);
div.setAttribute('id', alert_id);
div.appendChild(button);
div.appendChild(strong);
@@ -174,7 +172,7 @@ var socket,
div.parentNode.removeChild(div);
}
if (params.location == null)
if (params.location == null)
params.location = 'alert_window';
jQuery('#'+params.location).prepend(jQuery(div).fadeIn('100'));
@@ -194,20 +192,41 @@ var socket,
}
}
app.alertSuccess = function(message, timeout) {
if(!timeout)
timeout = 2000;
app.current_room = null;
app.alert({
title: 'Success',
message: message,
type: 'success',
timeout: timeout
});
}
app.alertError = function(message, timeout) {
if(!timeout)
timeout = 2000;
app.alert({
title: 'Error',
message: message,
type: 'error',
timeout: timeout
});
}
app.current_room = null;
app.enter_room = function(room) {
if(socket) {
if (app.current_room === room)
if (app.current_room === room)
return;
socket.emit('event:enter_room', {
'enter': room,
'leave': app.current_room
});
app.current_room = room;
}
};
@@ -218,7 +237,7 @@ var socket,
jQuery('.post-row').each(function() {
uids.push(this.getAttribute('data-uid'));
});
socket.emit('api:user.get_online_users', uids);
}
@@ -234,7 +253,7 @@ var socket,
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() {
@@ -249,13 +268,12 @@ var socket,
}
setTimeout(function() {
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
}, 100);
}
app.showLoginMessage = function() {
if(location.href.indexOf('loggedin') !== -1) {
function showAlert() {
app.alert({
type: 'success',
title: 'Welcome Back ' + app.username + '!',
@@ -263,15 +281,116 @@ var socket,
timeout: 5000
});
}
if(showWelcomeMessage) {
showWelcomeMessage = false;
if(document.readyState !== 'complete') {
$(document).ready(showAlert);
} else {
showAlert();
}
}
}
app.addCommasToNumbers = function() {
$('.formatted-number').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
}
app.createNewPosts = function(data) {
data.posts[0].display_moderator_tools = 'none';
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data),
uniqueid = new Date().getTime(),
tempContainer = jQuery('<div id="' + uniqueid + '"></div>')
.appendTo("#post-container")
.hide()
.append(html)
.fadeIn('slow');
for(var x=0,numPosts=data.posts.length;x<numPosts;x++) {
socket.emit('api:post.privileges', data.posts[x].pid);
}
tempContainer.replaceWith(tempContainer.contents());
infiniteLoaderActive = false;
app.populate_online_users();
app.addCommasToNumbers();
}
app.infiniteLoaderActive = false;
app.loadMorePosts = function(tid, callback) {
if(app.infiniteLoaderActive)
return;
app.infiniteLoaderActive = true;
socket.emit('api:topic.loadMore', {
tid: tid,
after: document.querySelectorAll('#post-container li[data-pid]').length
}, function(data) {
app.infiniteLoaderActive = false;
if(data.posts.length) {
app.createNewPosts(data);
}
if(callback)
callback(data.posts);
});
}
app.scrollToPost = function(pid) {
if(!pid)
return;
var container = $(document.body),
scrollTo = $('#post_anchor_' + pid),
tid = $('#post-container').attr('data-tid');
function animateScroll() {
$('body,html').animate({
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
});
}
if(!scrollTo.length && tid) {
var intervalID = setInterval(function() {
app.loadMorePosts(tid, function(posts) {
scrollTo = $('#post_anchor_' + pid);
if(tid && scrollTo.length) {
animateScroll();
}
if(!posts.length || scrollTo.length)
clearInterval(intervalID);
});
}, 100);
} else if(tid) {
animateScroll();
}
}
jQuery('document').ready(function() {
addTouchEvents();
$('#search-form').on('submit', function() {
var input = $(this).find('input');
ajaxify.go("search/"+input.val(), null, "search");
input.val('');
return false;
})
});
showWelcomeMessage = location.href.indexOf('loggedin') !== -1;
loadConfig();
function addTouchEvents() {
return; // later.
@@ -322,6 +441,6 @@ var socket,
el.off('mousemove');
onTouchEnd();
});
}
}());

View File

@@ -5,30 +5,46 @@
$(document).ready(function() {
var rep = $('#reputation');
rep.html(app.addCommas(rep.html()));
var postcount = $('#postcount');
postcount.html(app.addCommas(postcount.html()));
app.addCommasToNumbers();
var followBtn = $('#follow-btn');
if(yourid === "0") {
followBtn.hide();
}
else if(yourid !== theirid) {
if(isFollowing)
var unfollowBtn = $('#unfollow-btn');
if(yourid !== theirid) {
if(isFollowing) {
followBtn.hide();
else
unfollowBtn.show();
} else {
followBtn.show();
unfollowBtn.hide();
}
}
else {
followBtn.hide();
}
followBtn.on('click', function() {
followBtn.remove();
socket.emit('api:user.follow', {uid: theirid});
socket.emit('api:user.follow', {uid: theirid}, function(success) {
var username = $('.account-username a').html();
if(success) {
followBtn.hide();
unfollowBtn.show();
app.alertSuccess('You are now following ' + username + '!');
} else {
app.alertError('There was an error following' + username + '!');
}
});
return false;
});
unfollowBtn.on('click', function() {
socket.emit('api:user.unfollow', {uid: theirid}, function(success) {
var username = $('.account-username a').html();
if(success) {
followBtn.show();
unfollowBtn.hide();
app.alertSuccess('You are no longer following ' + username + '!');
} else {
app.alertError('There was an error unfollowing ' + username + '!');
}
});
return false;
});

View File

@@ -85,7 +85,11 @@ $(document).ready(function() {
type: type
};
socket.emit('api:user.changePicture', userData);
socket.emit('api:user.changePicture', userData, function(success) {
if(!success) {
app.alertError('There was an error changing picture!');
}
});
}
var selectedImageType = '';
@@ -102,7 +106,21 @@ $(document).ready(function() {
signature:$('#inputSignature').val()
};
socket.emit('api:user.updateProfile', userData);
socket.emit('api:user.updateProfile', userData, function(data) {
if(data.success) {
app.alertSuccess('Your profile has been updated successfully!');
if(data.picture) {
$('#user-current-picture').attr('src', data.picture);
$('#user_label img').attr('src', data.picture);
}
if(data.gravatarpicture) {
$('#user-gravatar-picture').attr('src', data.gravatarpicture);
gravatarPicture = data.gravatarpicture;
}
} else {
app.alertError('There was an error updating your profile!');
}
});
return false;
});
@@ -231,24 +249,29 @@ $(document).ready(function() {
password_confirm.on('keyup', onPasswordConfirmChanged);
$('#changePasswordBtn').on('click', function() {
if(passwordvalid && passwordsmatch && currentPassword.val()) {
socket.emit('api:user.changePassword', {
'currentPassword': currentPassword.val(),
'newPassword': password.val()
socket.emit('api:user.changePassword', {'currentPassword': currentPassword.val(),'newPassword': password.val() }, function(data) {
currentPassword.val('');
password.val('');
password_confirm.val('');
password_notify.html('');
password_confirm_notify.html('');
passwordsmatch = false;
passwordvalid = false;
if(data.err) {
app.alertError(data.err);
return;
}
app.alertSuccess('Your password is updated!');
});
}
return false;
});
socket.on('api:user.changePassword', function(data) {
currentPassword.val('');
password.val('');
password_confirm.val('');
password_notify.html('');
password_confirm_notify.html('');
passwordsmatch = false;
passwordvalid = false;
});
}());
});

View File

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

View File

@@ -9,7 +9,13 @@ $(document).ready(function() {
showemail: $('#showemailCheckBox').is(':checked')?1:0
};
socket.emit('api:user.saveSettings', settings);
socket.emit('api:user.saveSettings', settings, function(success) {
if(success) {
app.alertSuccess('Settings saved!');
} else {
app.alertError('There was an error saving settings!');
}
});
return false;
});

View File

@@ -74,6 +74,30 @@ jQuery('.blockclass').each(function() {
jQuery('.category_name, .category_description, .blockclass').on('change', function(ev) {
modified(ev.target);
});
jQuery('.entry-row button').each(function(index, element) {
var disabled = $(element).attr('data-disabled');
if(disabled == "0" || disabled == "")
$(element).html('Disable');
else
$(element).html('Enable');
});
jQuery('.entry-row button').on('click', function(ev) {
var btn = jQuery(this);
var categoryRow = btn.parents('li');
var cid = categoryRow.attr('data-cid');
var disabled = btn.html() == "Disable" ? "1":"0";
categoryRow.remove();
modified_categories[cid] = modified_categories[cid] || {};
modified_categories[cid]['disabled'] = disabled;
save();
return false;
});
});
}());

View File

@@ -0,0 +1,38 @@
var nodebb_admin = nodebb_admin || {};
(function() {
var plugins = {
init: function() {
var pluginsList = $('.plugins'),
numPlugins = pluginsList[0].querySelectorAll('li').length,
pluginID, pluginTgl;
if (numPlugins > 0) {
pluginsList.on('click', 'button[data-action="toggleActive"]', function() {
pluginID = $(this).parents('li').attr('data-plugin-id');
socket.emit('api:admin.plugins.toggle', pluginID);
});
socket.on('api:admin.plugins.toggle', function(status) {
pluginTgl = document.querySelector('.plugins li[data-plugin-id="' + status.id + '"] button');
pluginTgl.innerHTML = '<i class="icon-off"></i> ' + (status.active ? 'Dea' : 'A') + 'ctivate';
app.alert({
alert_id: 'plugin_toggled_' + status.id,
title: 'Plugin Enabled',
message: 'You may need to restart NodeBB in order for these changes to be reflected.',
type: 'notify',
timeout: 5000
})
});
} else {
pluginsList.append('<li><p><i>No plugins found.</i></p></li>');
}
}
};
jQuery(document).ready(function() {
nodebb_admin.plugins = plugins;
nodebb_admin.plugins.init();
});
})();

View File

@@ -39,7 +39,7 @@ var nodebb_admin = (function(nodebb_admin) {
(function() {
var scriptEl = document.createElement('script');
scriptEl.src = 'http://api.bootswatch.com?callback=nodebb_admin.themes.render';
scriptEl.src = 'http://api.bootswatch.com/2/?callback=nodebb_admin.themes.render';
document.body.appendChild(scriptEl);
var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
@@ -81,32 +81,38 @@ var nodebb_admin = (function(nodebb_admin) {
}, false);
// Installed Themes
socket.once('api:admin:themes.getInstalled', function(themes) {
socket.emit('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>' +
if (themes.length > 0) {
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>' +
'<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));
'<div class="clear">';
themeFrag.appendChild(liEl.cloneNode(true));
}
} else {
// No themes found
liEl.className = 'no-themes';
liEl.innerHTML = 'No installed themes found';
themeFrag.appendChild(liEl);
}
instListEl.innerHTML = '';
instListEl.appendChild(themeFrag);
});
socket.emit('api:admin:themes.getInstalled');
})();

View File

@@ -32,6 +32,23 @@ $(document).ready(function() {
socket.emit('api:admin.topics.getMore', {
limit: 10,
after: lastTid
}, function(topics) {
var btnEl = document.getElementById('topics_loadmore');
topics = JSON.parse(topics);
if (topics.length > 0) {
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
topics: topics
}),
topicsListEl = document.querySelector('.topics');
topicsListEl.innerHTML += html;
btnEl.innerHTML = 'Load More Topics';
} else {
// Exhausted all topics
btnEl.className += ' disabled';
btnEl.innerHTML = 'No more topics';
}
});
}
}, false);
@@ -40,9 +57,9 @@ $(document).ready(function() {
var topicEls = topicsListEl.querySelectorAll('li'),
numTopics = topicEls.length;
for(var x=0;x<numTopics;x++) {
if (topicEls[x].getAttribute('data-pinned')) topicEls[x].querySelector('[data-action="pin"]').className += ' active';
if (topicEls[x].getAttribute('data-locked')) topicEls[x].querySelector('[data-action="lock"]').className += ' active';
if (topicEls[x].getAttribute('data-deleted')) topicEls[x].querySelector('[data-action="delete"]').className += ' active';
if (topicEls[x].getAttribute('data-pinned') === '1') topicEls[x].querySelector('[data-action="pin"]').className += ' active';
if (topicEls[x].getAttribute('data-locked') === '1') topicEls[x].querySelector('[data-action="lock"]').className += ' active';
if (topicEls[x].getAttribute('data-deleted') === '1') topicEls[x].querySelector('[data-action="delete"]').className += ' active';
topicEls[x].removeAttribute('data-pinned');
topicEls[x].removeAttribute('data-locked');
topicEls[x].removeAttribute('data-deleted');
@@ -95,24 +112,4 @@ socket.on('api:topic.restore', function(response) {
$(btnEl).removeClass('active');
}
});
socket.on('api:admin.topics.getMore', function(topics) {
var btnEl = document.getElementById('topics_loadmore');
topics = JSON.parse(topics);
console.log(topics);
if (topics.length > 0) {
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
topics: topics
}),
topicsListEl = document.querySelector('.topics');
topicsListEl.innerHTML += html;
btnEl.innerHTML = 'Load More Topics';
} else {
// Exhausted all topics
btnEl.className += ' disabled';
btnEl.innerHTML = 'No more topics';
}
});

View File

@@ -8,6 +8,11 @@
return (parent.attr('data-admin') !== "0");
}
function isUserBanned(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-banned') !== "" && parent.attr('data-banned') !== "0");
}
function getUID(element) {
var parent = $(element).parents('.users-box');
return parent.attr('data-uid');
@@ -34,6 +39,20 @@
deleteBtn.show();
});
jQuery('.ban-btn').each(function(index, element) {
var banBtn = $(element);
var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
if(isAdmin)
banBtn.addClass('disabled');
else if(isBanned)
banBtn.addClass('btn-warning');
else
banBtn.removeClass('btn-warning');
});
jQuery('.admin-btn').on('click', function() {
var adminBtn = $(this);
var isAdmin = isUserAdmin(adminBtn);
@@ -74,13 +93,38 @@
return false;
});
jQuery('.ban-btn').on('click', function() {
var banBtn = $(this);
var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
var parent = banBtn.parents('.users-box');
var uid = getUID(banBtn);
if(!isAdmin) {
if(isBanned) {
socket.emit('api:admin.user.unbanUser', uid);
banBtn.removeClass('btn-warning');
parent.attr('data-banned', 0);
} else {
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') +'"?', function(confirm) {
socket.emit('api:admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
});
}
}
return false;
});
}
jQuery('document').ready(function() {
var yourid = templates.get('yourid');
var timeoutId = 0;
var yourid = templates.get('yourid'),
timeoutId = 0,
loadingMoreUsers = false;
var url = window.location.href,
parts = url.split('/'),
@@ -114,8 +158,7 @@
socket.removeAllListeners('api:admin.user.search');
socket.on('api:admin.user.search', function(data) {
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
users: data
}),
userListEl = document.querySelector('.users');
@@ -139,6 +182,45 @@
initUsers();
});
function onUsersLoaded(users) {
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({ users: users });
$('#users-container').append(html);
}
function loadMoreUsers() {
var set = '';
if(active === 'latest') {
set = 'users:joindate';
} else if(active === 'sort-posts') {
set = 'users:postcount';
} else if(active === 'sort-reputation') {
set = 'users:reputation';
}
if(set) {
loadingMoreUsers = true;
socket.emit('api:users.loadMore', {
set: set,
after: $('#users-container').children().length
}, function(data) {
if(data.users.length) {
onUsersLoaded(data.users);
}
loadingMoreUsers = false;
});
}
}
$('#load-more-users-btn').on('click', loadMoreUsers);
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
loadMoreUsers();
}
});
});
}());

View File

@@ -6,8 +6,9 @@
googleEl = document.getElementById('google-share'),
twitter_url = templates.get('twitter-intent-url'),
facebook_url = templates.get('facebook-share-url'),
google_url = templates.get('google-share-url');
google_url = templates.get('google-share-url'),
loadingMoreTopics = false;
app.enter_room(room);
twitterEl.addEventListener('click', function() {
@@ -26,7 +27,7 @@
var new_post = document.getElementById('new_post');
new_post.onclick = function() {
require(['composer'], function(cmp) {
cmp.push(0, cid);
cmp.push(0, cid);
});
}
@@ -34,7 +35,7 @@
'event:new_topic'
]);
socket.on('event:new_topic', function(data) {
function onNewTopic(data) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: [data] }),
topic = document.createElement('div'),
container = document.getElementById('topics-container'),
@@ -47,7 +48,7 @@
topic.innerHTML = html;
topic = topic.querySelector('a');
if (numTopics > 0) {
for(x=0;x<numTopics;x++) {
if (topics[x].querySelector('.icon-pushpin')) continue;
@@ -59,8 +60,11 @@
container.insertBefore(topic, null);
$(topic).hide().fadeIn('slow');
}
});
socket.emit('api:categories.getRecentReplies', cid);
}
socket.on('event:new_topic', onNewTopic);
socket.emit('api:categories.getRecentReplies', cid);
socket.on('api:categories.getRecentReplies', function(posts) {
@@ -71,7 +75,7 @@
var recent_replies = document.getElementById('category_recent_replies');
recent_replies.innerHTML = '';
var frag = document.createDocumentFragment(),
li = document.createElement('li');
for (var i=0,numPosts=posts.length; i<numPosts; i++) {
@@ -80,7 +84,7 @@
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 + '">' +
'<a href="/topic/'+ posts[i].topicSlug + '#' + posts[i].pid + '">' +
'<p>' +
'<strong>' + posts[i].username + '</strong>: ' + posts[i].content +
'</p>' +
@@ -92,4 +96,39 @@
}
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: topics }),
container = $('#topics-container');
jQuery('#topics-container, .category-sidebar').removeClass('hidden');
jQuery('#category-no-topics').remove();
container.append(html);
}
function loadMoreTopics(cid) {
loadingMoreTopics = true;
socket.emit('api:category.loadMore', {
cid: cid,
after: $('#topics-container').children().length
}, function(data) {
if(data.topics.length) {
onTopicsLoaded(data.topics);
}
loadingMoreTopics = false;
});
}
$(window).off('scroll').on('scroll', function(ev) {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics(cid);
}
});
})();

View File

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

View File

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

View File

@@ -10,29 +10,29 @@
$('#no-following-notice').show();
}
if(yourid !== theirid) {
$('.unfollow-btn').hide();
}
else {
$('.unfollow-btn').on('click',function() {
var removeBtn = $(this);
var unfollowBtn = $(this);
var followingUid = $(this).attr('followingUid');
removeBtn.parent().remove();
socket.emit('api:user.unfollow', {uid: followingUid});
socket.emit('api:user.unfollow', {uid: followingUid}, function(success) {
var username = unfollowBtn.attr('data-username');
if(success) {
unfollowBtn.parent().remove();
app.alertSuccess('You are no longer following ' + username + '!');
} else {
app.alertError('There was an error unfollowing ' + username + '!');
}
});
return false;
});
}
$('.reputation').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
$('.postcount').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
app.addCommasToNumbers();
});

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
(function() {
var loadingMoreTopics = false;
app.enter_room('recent_posts');
ajaxify.register_events([
@@ -44,7 +46,35 @@
socket.on('event:new_post', function(data) {
++newPostCount;
updateAlertText();
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['recent'].blocks['topics']).parse({ topics: topics }),
container = $('#topics-container');
$('#category-no-topics').remove();
container.append(html);
}
function loadMoreTopics() {
loadingMoreTopics = true;
socket.emit('api:topics.loadMoreRecentTopics', {after:$('#topics-container').children().length}, function(data) {
if(data.topics && data.topics.length) {
onTopicsLoaded(data.topics);
}
loadingMoreTopics = false;
});
}
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics();
}
});
})();

View File

@@ -22,12 +22,13 @@
if(username.value.length < 3) {
username_notify.innerHTML = 'Username too short';
username_notify.className = 'label label-important';
}
else if(!usernamevalid) {
} else if(username.value.length > 13) {
username_notify.innerHTML = 'Username too long';
username_notify.className = 'label label-important';
} else if(!usernamevalid) {
username_notify.innerHTML = 'Invalid username';
username_notify.className = 'label label-important';
}
else {
} else {
socket.emit('user.exists', {username: username.value});
}
});

View File

@@ -0,0 +1,14 @@
(function() {
$(document).ready(function() {
var searchQuery = $('#topics-container').attr('data-search-query');
$('.search-result-text').each(function() {
var text = $(this).html();
var regex = new RegExp(searchQuery, 'gi');
text = text.replace(regex, '<span class="label label-success">'+searchQuery+'</span>');
$(this).html(text);
});
});
})();

View File

@@ -8,19 +8,13 @@
deleted: templates.get('deleted'),
pinned: templates.get('pinned')
},
topic_name = templates.get('topic_name'),
infiniteLoaderActive = false;
topic_name = templates.get('topic_name');
function addCommasToNumbers() {
$('.formatted-number').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
}
jQuery('document').ready(function() {
addCommasToNumbers();
app.addCommasToNumbers();
var room = 'topic_' + tid,
adminTools = document.getElementById('thread-tools');
@@ -32,17 +26,12 @@
if (thread_state.pinned === '1') set_pinned_state(true);
if (expose_tools === '1') {
var deleteThreadEl = document.getElementById('delete_thread'),
lockThreadEl = document.getElementById('lock_thread'),
pinThreadEl = document.getElementById('pin_thread'),
moveThreadEl = document.getElementById('move_thread'),
moveThreadModal = $('#move_thread_modal');
var moveThreadModal = $('#move_thread_modal');
adminTools.style.visibility = 'inherit';
// Add events to the thread tools
deleteThreadEl.addEventListener('click', function(e) {
e.preventDefault();
$('#delete_thread').on('click', function(e) {
if (thread_state.deleted !== '1') {
bootbox.confirm('Are you sure you want to delete this thread?', function(confirm) {
if (confirm) socket.emit('api:topic.delete', { tid: tid });
@@ -52,30 +41,31 @@
if (confirm) socket.emit('api:topic.restore', { tid: tid });
});
}
}, false);
return false;
});
lockThreadEl.addEventListener('click', function(e) {
e.preventDefault();
$('#lock_thread').on('click', function(e) {
if (thread_state.locked !== '1') {
socket.emit('api:topic.lock', { tid: tid });
} else {
socket.emit('api:topic.unlock', { tid: tid });
}
}, false);
return false;
});
pinThreadEl.addEventListener('click', function(e) {
e.preventDefault();
$('#pin_thread').on('click', function(e) {
if (thread_state.pinned !== '1') {
socket.emit('api:topic.pin', { tid: tid });
} else {
socket.emit('api:topic.unpin', { tid: tid });
}
}, false);
return false;
});
moveThreadEl.addEventListener('click', function(e) {
e.preventDefault();
$('#move_thread').on('click', function(e) {
moveThreadModal.modal('show');
}, false);
return false;
});
moveThreadModal.on('shown', function() {
var loadingEl = document.getElementById('categories-loading');
@@ -201,24 +191,25 @@
});
}
});
socket.emit('api:topic.followCheck', tid);
followEl[0].addEventListener('click', function() {
socket.emit('api:topic.follow', tid);
}, false);
if(followEl[0]) {
followEl[0].addEventListener('click', function() {
socket.emit('api:topic.follow', tid);
}, false);
}
// Infinite scrolling of posts
window.addEventListener('scroll', function() {
var windowHeight = document.body.offsetHeight - $(window).height(),
half = windowHeight / 2;
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if (document.body.scrollTop > half && !infiniteLoaderActive) {
infiniteLoaderActive = true;
socket.emit('api:topic.loadMore', {
tid: tid,
after: document.querySelectorAll('#post-container li[data-pid]').length
});
if ($(window).scrollTop() > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) {
app.loadMorePosts(tid);
}
});
$('.post-container').on('click', '.deleted', function(ev) {
$(this).toggleClass('deleted-expanded');
});
});
var reply_fn = function() {
@@ -261,11 +252,9 @@
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});
}
});
@@ -291,19 +280,21 @@
socket.emit('api:posts.delete', { pid: pid }) :
socket.emit('api:posts.restore', { pid: pid });
}
});
});
$('.post-container').delegate('.chat', 'click', function(e) {
var username = $(this).parents('li').attr('data-username');
var touid = $(this).parents('li').attr('data-uid');
require(['chat'], function(chat){
if(username === app.username || !app.username)
return;
require(['chat'], function(chat) {
var chatModal = chat.createModalIfDoesntExist(username, touid);
chatModal.show();
chat.bringModalToTop(chatModal);
});
});
ajaxify.register_events([
@@ -331,26 +322,7 @@
adjust_rep(-1, data.pid, data.uid);
});
socket.on('event:new_post', function(data) {
data.posts[0].display_moderator_tools = 'none';
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data),
uniqueid = new Date().getTime(),
tempContainer = jQuery('<div id="' + uniqueid + '"></div>')
.appendTo("#post-container")
.hide()
.append(html)
.fadeIn('slow');
for(var x=0,numPosts=data.posts.length;x<numPosts;x++) {
socket.emit('api:post.privileges', data.posts[x].pid);
}
tempContainer.replaceWith(tempContainer.contents());
infiniteLoaderActive = false;
app.populate_online_users();
addCommasToNumbers();
});
socket.on('event:new_post', app.createNewPosts);
socket.on('event:topic_deleted', function(data) {
if (data.tid === tid && data.status === 'ok') {
@@ -398,7 +370,7 @@
var editedPostEl = document.getElementById('content_' + data.pid);
var editedPostTitle = $('#topic_title_'+data.pid);
if(editedPostTitle.length > 0) {
editedPostTitle.fadeOut(250, function() {
editedPostTitle.html(data.title);
@@ -413,9 +385,22 @@
});
socket.on('api:posts.favourite', function(data) {
if (data.status !== 'ok' && data.pid) {
if (data.status === 'ok' && data.pid) {
var favEl = document.querySelector('.post_rep_' + data.pid).nextSibling;
if (favEl) favEl.className = 'icon-star-empty';
if (favEl) {
favEl.className = 'icon-star';
$(favEl).parent().addClass('btn-warning');
}
}
});
socket.on('api:posts.unfavourite', function(data) {
if (data.status === 'ok' && data.pid) {
var favEl = document.querySelector('.post_rep_' + data.pid).nextSibling;
if (favEl) {
favEl.className = 'icon-star-empty';
$(favEl).parent().removeClass('btn-warning');
}
}
});
@@ -608,7 +593,7 @@
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
editEl = postEl.find('.edit'),
deleteEl = postEl.find('.delete');
if (state) {
editEl.removeClass('none');
deleteEl.removeClass('none');

109
public/src/forum/unread.js Normal file
View File

@@ -0,0 +1,109 @@
(function() {
var loadingMoreTopics = false;
app.enter_room('recent_posts');
ajaxify.register_events([
'event:new_topic',
'event:new_post'
]);
var newTopicCount = 0, newPostCount = 0;
$('#new-topics-alert').on('click', function() {
$(this).hide();
});
socket.on('event:new_topic', function(data) {
++newTopicCount;
updateAlertText();
});
function updateAlertText() {
var text = '';
if(newTopicCount > 1)
text = 'There are ' + newTopicCount + ' new topics';
else if(newTopicCount === 1)
text = 'There is 1 new topic';
else
text = 'There are no new topics';
if(newPostCount > 1)
text += ' and ' + newPostCount + ' new posts.';
else if(newPostCount === 1)
text += ' and 1 new post.';
else
text += ' and no new posts.';
text += ' Click here to reload.';
$('#new-topics-alert').html(text).fadeIn('slow');
}
socket.on('event:new_post', function(data) {
++newPostCount;
updateAlertText();
});
$('#mark-allread-btn').on('click', function() {
var btn = $(this);
socket.emit('api:topics.markAllRead', {} , function(success) {
if(success) {
btn.remove();
$('#topics-container').empty();
$('#category-no-topics').removeClass('hidden');
app.alertSuccess('All topics marked as read!');
$('#numUnreadBadge')
.removeClass('badge-important')
.addClass('badge-inverse')
.html('0');
} else {
app.alertError('There was an error marking topics read!');
}
});
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['unread'].blocks['topics']).parse({ topics: topics }),
container = $('#topics-container');
$('#category-no-topics').remove();
container.append(html);
}
function loadMoreTopics() {
loadingMoreTopics = true;
socket.emit('api:topics.loadMoreUnreadTopics', {after:parseInt($('#topics-container').attr('data-next-start'), 10)}, function(data) {
if(data.topics && data.topics.length) {
onTopicsLoaded(data.topics);
$('#topics-container').attr('data-next-start', data.nextStart);
} else {
$('#load-more-btn').hide();
}
loadingMoreTopics = false;
});
}
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics();
}
});
if($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20)
$('#load-more-btn').show();
$('#load-more-btn').on('click', function() {
loadMoreTopics();
});
})();

View File

@@ -2,11 +2,14 @@
$(document).ready(function() {
var timeoutId = 0;
var loadingMoreUsers = false;
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length-1];
app.addCommasToNumbers();
jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
if (this.getAttribute('href').match(active)) {
@@ -66,14 +69,48 @@
});
$('.reputation').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
$('.postcount').each(function(index, element) {
$(element).html(app.addCommas($(element).html()));
});
function onUsersLoaded(users) {
var html = templates.prepare(templates['users'].blocks['users']).parse({ users: users });
$('#users-container').append(html);
}
function loadMoreUsers() {
var set = '';
if(active === 'users-latest' || active === 'users') {
set = 'users:joindate';
} else if(active === 'users-sort-posts') {
set = 'users:postcount';
} else if(active === 'users-sort-reputation') {
set = 'users:reputation';
}
if(set) {
loadingMoreUsers = true;
socket.emit('api:users.loadMore', {
set: set,
after: $('#users-container').children().length
}, function(data) {
if(data.users.length) {
onUsersLoaded(data.users);
} else {
$('#load-more-users-btn').addClass('disabled');
}
loadingMoreUsers = false;
});
}
}
$('#load-more-users-btn').on('click', loadMoreUsers);
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreUsers) {
loadMoreUsers();
}
});
});
}());

View File

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

View File

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

View File

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

View File

@@ -86,12 +86,12 @@
// from http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
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;
var valid = email.indexOf('@') !== -1 ? true : false;
return valid;
},
isUserNameValid: function(name) {
return (name && name !== "" && !(/^\s*$/.test(name)));
return (name && name !== "" && (/^[a-zA-Z0-9 _-]{3,14}$/.test(name)));
},
isPasswordValid: function(password) {
@@ -124,6 +124,40 @@
}
return tags;
},
refreshTitle: function(url) {
if (!url) {
var a = document.createElement('a');
a.href = document.location;
url = a.pathname.slice(1);
}
socket.emit('api:meta.buildTitle', url, function(title, numNotifications) {
document.title = (numNotifications > 0 ? '(' + numNotifications + ') ' : '') + title;
if (numNotifications > 0) document.querySelector('.notifications a i').className = 'icon-circle active';
});
jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) {
var badge = jQuery('#numUnreadBadge');
badge.html(data.count > 20 ? '20+' : data.count);
if (data.count > 0) {
badge
.removeClass('badge-inverse')
.addClass('badge-important')
}
else {
badge
.removeClass('badge-important')
.addClass('badge-inverse')
}
});
},
isRelativeUrl: function(url) {
var firstChar = url.slice(0, 1);
return (firstChar === '.' || firstChar === '/');
}
}

View File

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

View File

@@ -60,17 +60,11 @@
</div>
</div>
<div class="account-username-box">
<div class="account-username-box" data-userslug="{userslug}">
<span class="account-username">
<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="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">
@@ -171,4 +165,5 @@
<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/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountedit.js"></script>

View File

@@ -1,17 +1,11 @@
<div class="well">
<div class="account-username-box">
<div class="account-username-box" data-userslug="{userslug}">
<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">
@@ -31,4 +25,5 @@
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/accountheader.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/accountsettings.js"></script>

View File

@@ -27,6 +27,7 @@
<option value="category-orange">category-orange</option>
</select>
<input data-name="description" placeholder="Category Description" value="{categories.description}" class="category_description input-medium description"></input>
<button type="submit" class="btn" data-disabled="{categories.disabled}">Disable</button>
</form>
</li>

View File

@@ -27,6 +27,8 @@
</script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
<script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script src="{relative_path}/src/utils.js"></script>
<link rel="stylesheet" type="text/css" href="{relative_path}/css/style.css" />
<link rel="stylesheet" type="text/css" href="{relative_path}/css/admin.css" />
</head>
@@ -70,10 +72,11 @@
<ul class="nav nav-list">
<li class="nav-header">NodeBB</li>
<li class='active'><a href='{relative_path}/admin/index'><i class='icon-home'></i> Home</a></li>
<li class=''><a href='{relative_path}/admin/categories'><i class='icon-folder-close-alt'></i> Categories</a></li>
<li class=''><a href='{relative_path}/admin/categories/active'><i class='icon-folder-close-alt'></i> Categories</a></li>
<li class=''><a href='{relative_path}/admin/users/latest'><i class='icon-user'></i> Users</a></li>
<li class=''><a href='{relative_path}/admin/topics'><i class='icon-book'></i> Topics</a></li>
<li class=''><a href='{relative_path}/admin/themes'><i class='icon-th'></i> Themes</a></li>
<li class=''><a href='{relative_path}/admin/plugins'><i class='icon-code-fork'></i> Plugins</a></li>
<li class=''><a href='{relative_path}/admin/settings'><i class='icon-cogs'></i> Settings</a></li>
<li class=''><a href='{relative_path}/admin/redis'><i class='icon-hdd'></i> Redis</a></li>
<li class=''><a href="{relative_path}/admin/motd"><i class="icon-comment"></i> MOTD</a></li>

View File

@@ -0,0 +1,25 @@
<h1>Plugins</h1>
<ul class="plugins">
<!-- BEGIN plugins -->
<li data-plugin-id="{plugins.id}">
<h2>{plugins.name}</h2>
<div class="pull-right">
<button data-action="toggleActive" class="btn btn-primary">{plugins.activeText}</button>
</div>
<p>{plugins.description}</p>
<p>For more information: <a href="{plugins.url}">{plugins.url}</a></p>
</li>
<!-- END plugins -->
</ul>
<div class="alert">
<p>
<strong>Interesed in writing plugins for NodeBB?</strong>
</p>
<p>
Full documentation regarding plugin authoring can be found in the <a target="_blank" href="https://github.com/designcreateplay/NodeBB/wiki/Writing-Plugins-for-NodeBB">NodeBB Wiki</a>.
</p>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/plugins.js"></script>

View File

@@ -16,6 +16,7 @@
<hr/>
<span>Total Connections Received</span> <span class="text-right">{total_connections_received}</span><br/>
<span>Total Commands Processed</span> <span class="text-right">{total_commands_processed}</span><br/>
<span>Instantaneous Ops. Per Second</span> <span class="text-right">{instantaneous_ops_per_sec}</span><br/>
<hr/>
<span>Keyspace Hits</span> <span class="text-right">{keyspace_hits}</span><br/>
<span>Keyspace Misses</span> <span class="text-right">{keyspace_misses}</span><br/>

View File

@@ -8,6 +8,8 @@
<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" />
<label>Imgur Client ID</label>
<input type="text" class="input-xxlarge" placeholder="Imgur ClientID for image uploads" data-field="imgurClientID" />
</form>
</div>
@@ -48,6 +50,16 @@
</div>
</form>
<form>
<h3>Post Settings</h3>
<div class="alert alert-notify">
<strong>Post Delay</strong><br /> <input type="text" class="" value="10000" data-field="postDelay"><br />
<strong>Minimum Title Length</strong><br /> <input type="text" class="" value="3" data-field="minimumTitleLength"><br />
<strong>Minimum Post Length</strong><br /> <input type="text" class="" value="8" data-field="minimumPostLength"><br />
</div>
</form>
<button class="btn btn-large btn-primary" id="save">Save</button>
<script>

View File

@@ -14,9 +14,9 @@
<span id="user-notfound-notify" class="label label-important hide">User not found!</span><br/>
</div>
<ul class="users">
<ul id="users-container" class="users">
<!-- BEGIN users -->
<div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}">
<div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}" data-banned="{users.banned}">
<a href="/users/{users.userslug}">
<img src="{users.picture}" class="img-polaroid"/>
</a>
@@ -37,10 +37,16 @@
<div>
<a href="#" class="btn delete-btn btn-danger">Delete</a>
</div>
<div>
<a href="#" class="btn ban-btn">Ban</a>
</div>
</div>
<!-- END users -->
</ul>
<div class="text-center {loadmore_display}">
<button id="load-more-users-btn" class="btn">Load More</button>
</div>
<input type="hidden" template-variable="yourid" value="{yourid}" />

View File

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

View File

@@ -7,6 +7,7 @@
"admin/redis[^]*": "admin/redis",
"admin/index[^]*": "admin/index",
"admin/themes[^]*": "admin/themes",
"admin/plugins[^]*": "admin/plugins",
"admin/settings[^]*": "admin/settings",
"admin/twitter[^]*": "admin/twitter",
"admin/facebook[^]*": "admin/facebook",
@@ -24,11 +25,17 @@
"users[^]*following": "following",
"users[^]*followers": "followers",
"users[^]*settings": "accountsettings",
"users[^]*favourites": "favourites",
"users/[^]*": "account",
"recent": "recent",
"unread": "unread",
"popular": "category",
"active": "category"
"active": "category",
"search": "search",
"reset/[^]*": "reset_code",
"reset": "reset"
},
"force_refresh": {
"logout": true

View File

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

View File

@@ -1,17 +1,11 @@
<div class="well">
<div class="account-username-box">
<div class="account-username-box" data-userslug="{userslug}">
<span class="account-username">
<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="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>
@@ -24,15 +18,14 @@
<a href="/users/{followers.userslug}">{followers.username}</a>
<br/>
<div title="reputation">
<span class='reputation'>{followers.reputation}</span>
<span class='formatted-number'>{followers.reputation}</span>
<i class='icon-star'></i>
</div>
<div title="post count">
<span class='postcount'>{followers.postcount}</span>
<span class='formatted-number'>{followers.postcount}</span>
<i class='icon-pencil'></i>
</div>
</div>
<!-- END followers -->
</div>
<div id="no-followers-notice" class="alert alert-warning hide">This user doesn't have any followers :(</div>

View File

@@ -1,24 +1,15 @@
<div class="well">
<div class="account-username-box">
<div class="account-username-box" data-userslug="{userslug}">
<span class="account-username">
<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="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>
<!-- BEGIN following -->
<div class="users-box">
<a href="/users/{following.userslug}">
<img src="{following.picture}" class="img-polaroid"/>
@@ -27,16 +18,15 @@
<a href="/users/{following.userslug}">{following.username}</a>
<br/>
<div title="reputation">
<span class='reputation'>{following.reputation}</span>
<span class='formatted-number'>{following.reputation}</span>
<i class='icon-star'></i>
</div>
<div title="post count">
<span class='postcount'>{following.postcount}</span>
<span class='formatted-number'>{following.postcount}</span>
<i class='icon-pencil'></i>
</div>
<a id="unfollow-btn" href="#" class="btn unfollow-btn" followingUid="{following.uid}">Unfollow</a>
<a id="unfollow-btn" href="#" class="btn unfollow-btn" followingUid="{following.uid}" data-username="{following.username}">Unfollow</a>
</div>
<!-- END following -->
</div>
<div id="no-following-notice" class="alert alert-warning hide">This user isn't following anyone :(</div>

View File

@@ -9,11 +9,11 @@
<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>
<script type="text/javascript" src="{relative_path}/socket.io/socket.io.js"></script>
<script type="text/javascript" src="{relative_path}/src/app.js"></script>
<script src="http://code.jquery.com/jquery.js"></script>
<script src="{relative_path}/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js"></script>
<script src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
<script src="{relative_path}/socket.io/socket.io.js"></script>
<script src="{relative_path}/src/app.js"></script>
<script src="{relative_path}/vendor/requirejs/require.js"></script>
<script src="{relative_path}/vendor/bootbox/bootbox.min.js"></script>
<script>
@@ -22,14 +22,13 @@
waitSeconds: 3
});
</script>
<script type="text/javascript" src="{relative_path}/src/templates.js"></script>
<script type="text/javascript" src="{relative_path}/src/ajaxify.js"></script>
<script type="text/javascript" src="{relative_path}/src/jquery.form.js"></script>
<script type="text/javascript" src="{relative_path}/src/utils.js"></script>
<script src="{relative_path}/src/templates.js"></script>
<script src="{relative_path}/src/ajaxify.js"></script>
<script src="{relative_path}/src/jquery.form.js"></script>
<script src="{relative_path}/src/utils.js"></script>
<link rel="stylesheet" type="text/css" href="{relative_path}/css/nodebb.css" />
</head>
<body>
@@ -47,14 +46,11 @@
<div class="nav-collapse collapse">
<ul id="main-nav" class="nav nodebb-inline-block">
<li>
<a href="/recent">Recent <!--<span class="badge badge-inverse">3</span>--></a>
<a href="/recent">Recent</a>
</li>
<!--<li>
<a href="/popular">Popular</a>
<li class="nodebb-loggedin">
<a href="/unread"><span id="numUnreadBadge" class="badge badge-inverse">0</span> Unread</a>
</li>
<li>
<a href="/active">Active</a>
</li>-->
<li>
<a href="/users">Users</a>
</li>
@@ -66,6 +62,13 @@
<li><a href="#"><i class="icon-refresh icon-spin"></i> Loading Notifications</a></li>
</ul>
</li>
<li>
<form id="search-form" class="form-search form-inline" action="" method="GET">
<input type="text" name="query" class="input-medium search-query">
<button type="submit" class="btn hide">Search</button>
</form>
</li>
</ul>
</div>
</div>

View File

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

View File

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

View File

@@ -7,15 +7,13 @@
</div>
<div class="alert alert-warning hide {no_topics_message}" id="category-no-topics">
<strong>There are no topics in this category.</strong><br />
Why don't you try posting one?
</div>
<a href="/recent">
<div class="alert hide" id="new-topics-alert"></div>
</a>
<div class="alert alert-warning hide {no_topics_message}" id="category-no-topics">
<strong>There are no recent topics.</strong>
</div>
<div class="category row">
<div class="{topic_row_size}">

View File

@@ -0,0 +1,50 @@
<div class="container">
<ul class="breadcrumb">
<li><a href="/">Home</a><span class="divider">/</span></li>
<li class="active">Search</li>
</ul>
</div>
<div class="alert alert-warning {show_no_results}" id="no-search-results">
<strong>No search results for {search_query}.</strong>
</div>
<div class="category row">
<div class="span12">
<ul id="topics-container" data-search-query="{search_query}">
<!-- BEGIN topics -->
<a href="../../topic/{topics.slug}" id="tid-{topics.tid}">
<li class="category-item">
<div class="row-fluid">
<div class="span12 img-polaroid">
<div class="search-result-post">
<img src="{topics.teaser_userpicture}" />
<strong>{topics.teaser_username}</strong>: <span class="search-result-text">{topics.title}</span>
</div>
</div>
</div>
</li>
</a>
<!-- END topics -->
<!-- BEGIN posts -->
<a href="../../topic/{posts.topicSlug}#{posts.pid}" id="tid-{posts.tid}">
<li class="category-item">
<div class="row-fluid">
<div class="span12 img-polaroid">
<div class="search-result-post">
<img src="{posts.picture}" />
<strong>{posts.username}</strong>: <span class="search-result-text">{posts.content}</span>
</div>
</div>
</div>
</li>
</a>
<!-- END posts -->
</ul>
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/search.js"></script>

View File

@@ -15,12 +15,12 @@
</ul>
</div>
<ul id="post-container" class="post-container container">
<ul id="post-container" class="post-container container" data-tid="{topic_id}">
<!-- BEGIN main_posts -->
<a name="{main_posts.pid}"></a>
<a id="post_anchor_{main_posts.pid}" name="{main_posts.pid}"></a>
<li class="row post-row main-post" data-pid="{main_posts.pid}" data-uid="{main_posts.uid}" data-deleted="{main_posts.deleted}">
<div class="span12">
<div class="post-block">
@@ -42,7 +42,7 @@
<button id="ids_{main_posts.pid}_{main_posts.uid}" class="btn delete {main_posts.display_moderator_tools}" type="button" title="Delete"><i class="icon-trash"></i></button>
<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">
<button id="favs_{main_posts.pid}_{main_posts.uid}" class="favourite btn {main_posts.fav_button_class}" 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>
@@ -78,9 +78,9 @@
<!-- END main_posts -->
<!-- BEGIN posts -->
<a name="{posts.pid}"></a>
<li class="row post-row" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-deleted="{posts.deleted}">
<div class="span1 profile-image-block visible-desktop">
<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
<li class="row-fluid post-row" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-deleted="{posts.deleted}">
<div class="span1 profile-image-block hidden-phone hidden-tablet">
<!--<i class="icon-spinner icon-spin icon-2x pull-left"></i>-->
<a href="/users/{posts.userslug}">
<img src="{posts.picture}" align="left" class="img-polaroid"/>
@@ -89,9 +89,10 @@
<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>
<span class="label label-important {posts.show_banned}">banned</span>
</div>
<div class="span11">
<div class="post-block">
<div class="span11 span12-tablet">
<div class="post-block speech-bubble">
<div id="content_{posts.pid}" class="post-content">{posts.content}</div>
<div id="images_{posts.pid}" class="post-images">
<!-- BEGIN uploadedImages -->

View File

@@ -0,0 +1,55 @@
<div class="container">
<ul class="breadcrumb">
<li><a href="/">Home</a><span class="divider">/</span></li>
<li class="active">{category_name}</li>
<div id="category_active_users"></div>
</ul>
</div>
<a href="/unread">
<div class="alert hide" id="new-topics-alert"></div>
</a>
<div class="alert alert-warning {no_topics_message}" id="category-no-topics">
<strong>There are no unread topics.</strong>
</div>
<div>
<button id="mark-allread-btn" class="btn {show_markallread_button}">Mark All As Read</button>
</div>
<div class="category row">
<div class="{topic_row_size}">
<ul id="topics-container" data-next-start="{nextStart}">
<!-- BEGIN topics -->
<a href="../../topic/{topics.slug}" id="tid-{topics.tid}">
<li class="category-item {topics.deleted-class}">
<div class="row-fluid">
<div class="span12 topic-row img-polaroid">
<div class="latest-post visible-desktop">
<div class="pull-right">
<img style="width: 48px; height: 48px; /*temporary*/" src="{topics.teaser_userpicture}" />
<p><strong>{topics.teaser_username}</strong>: {topics.teaser_text}</p>
<span>posted {topics.teaser_timestamp} ago</span>
</div>
</div>
<div>
<h3><span class="topic-title"><span class="badge {topics.badgeclass}">{topics.postcount}</span>{topics.title}</span></h3>
<small>
<strong><i class="{topics.pin-icon}"></i><i class="{topics.lock-icon}"></i></strong>
Posted {topics.relativeTime} ago by
<strong>{topics.username}</strong>.
</small>
</div>
</div>
</div>
</li>
</a>
<!-- END topics -->
</ul>
<button id="load-more-btn" class="btn hide">Load More</button>
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/unread.js"></script>

View File

@@ -14,7 +14,7 @@
<span id="user-notfound-notify" class="label label-important hide">User not found!</span><br/>
</div>
<ul class="users">
<ul id="users-container" class="users">
<!-- BEGIN users -->
<div class="users-box">
<a href="/users/{users.userslug}">
@@ -24,11 +24,11 @@
<a href="/users/{users.userslug}">{users.username}</a>
<br/>
<div title="reputation">
<span class='reputation'>{users.reputation}</span>
<span class='formatted-number'>{users.reputation}</span>
<i class='icon-star'></i>
</div>
<div title="post count">
<span class='postcount'>{users.postcount}</span>
<span class='formatted-number'>{users.postcount}</span>
<i class='icon-pencil'></i>
</div>
</div>
@@ -36,4 +36,8 @@
</ul>
</div>
<div class="text-center {loadmore_display}">
<button id="load-more-users-btn" class="btn">Load More</button>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/users.js"></script>

View File

@@ -18,7 +18,8 @@ var RDB = require('./../redis.js'),
icon: data.icon,
blockclass: data.blockclass,
slug: slug,
topic_count: 0
topic_count: 0,
disabled: 0
});
RDB.set('categoryslug:' + slug + ':cid', cid);

View File

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

View File

@@ -3,12 +3,13 @@ var RDB = require('./redis.js'),
utils = require('./../public/src/utils.js'),
user = require('./user.js'),
async = require('async'),
topics = require('./topics.js');
topics = require('./topics.js'),
winston = require('winston');
(function(Categories) {
Categories.getCategoryById = function(category_id, current_user, callback) {
Categories.getCategoryData(category_id, function(err, categoryData) {
if (err) return callback(err);
@@ -17,8 +18,9 @@ var RDB = require('./redis.js'),
category_description = categoryData.description;
function getTopicIds(next) {
Categories.getTopicIds(category_id, next);
Categories.getTopicIds(category_id, 0, 19, next);
}
function getActiveUsers(next) {
Categories.getActiveUsers(category_id, next);
}
@@ -37,25 +39,17 @@ var RDB = require('./redis.js'),
'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)
'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug) + '&text=' + encodeURIComponent(category_name),
'facebook-share-url': 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug),
'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug)
};
function getTopics(next) {
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);
topics.getTopicsByTids(tids, current_user, function(topicsData) {
next(null, topicsData);
}, category_id);
}
function getModerators(next) {
Categories.getModerators(category_id, next);
}
@@ -72,7 +66,6 @@ var RDB = require('./redis.js'),
categoryData.moderators = moderators;
categoryData.show_sidebar = 'hidden';
categoryData.no_topics_message = 'show';
callback(null, categoryData);
});
} else {
@@ -89,128 +82,22 @@ var RDB = require('./redis.js'),
});
}
Categories.getTopicIds = function(cid, callback) {
RDB.smembers('categories:' + cid + ':tid', callback);
Categories.getCategoryTopics = function(cid, start, stop, uid, callback) {
Categories.getTopicIds(cid, start, stop, function(err, tids) {
topics.getTopicsByTids(tids, uid, function(topicsData) {
callback(topicsData);
}, cid);
});
}
Categories.getTopicIds = function(cid, start, stop, callback) {
RDB.zrevrange('categories:' + cid + ':tid', start, stop, 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) {
var latestTopics = {
'category_name' : 'Recent',
'show_sidebar' : 'hidden',
'show_topic_button' : 'hidden',
'no_topics_message' : 'hidden',
'topic_row_size': 'span12',
'category_id': false,
'topics' : []
};
if (!tids.length) {
callback(latestTopics);
return;
}
Categories.getTopicsByTids(tids, current_user, function(topicData) {
latestTopics.topics = topicData;
callback(latestTopics);
});
});
}
// not the permanent location for this function
Categories.getTopicsByTids = function(tids, current_user, callback, category_id /*temporary*/) {
var retrieved_topics = [];
function getTopicInfo(topicData, callback) {
function getUserName(next) {
user.getUserField(topicData.uid, 'username', function(username) {
next(null, username);
});
}
function hasReadTopic(next) {
topics.hasReadTopic(topicData.tid, current_user, function(hasRead) {
next(null, hasRead);
});
}
function getTeaserInfo(next) {
topics.getTeaser(topicData.tid, function(err, teaser) {
next(null, teaser || {});
});
}
// temporary. I don't think this call should belong here
function getPrivileges(next) {
Categories.privileges(category_id, current_user, function(user_privs) {
next(null, user_privs);
});
}
async.parallel([getUserName, hasReadTopic, getTeaserInfo, getPrivileges], function(err, results) {
var username = results[0],
hasReadTopic = results[1],
teaserInfo = results[2],
privileges = results[3];
callback({
username: username,
hasread: hasReadTopic,
teaserInfo: teaserInfo,
privileges: privileges
});
});
}
function isTopicVisible(topicData, topicInfo) {
var deleted = parseInt(topicData.deleted, 10) !== 0;
return !deleted || (deleted && topicInfo.privileges.view_deleted) || topicData.uid === current_user;
}
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';
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : '';
topicData.relativeTime = utils.relativeTime(topicData.timestamp);
topicData.username = topicInfo.username;
topicData.badgeclass = (topicInfo.hasread && current_user != 0) ? '' : 'badge-important';
topicData.teaser_text = topicInfo.teaserInfo.text || '',
topicData.teaser_username = topicInfo.teaserInfo.username || '';
topicData.teaser_userpicture = topicInfo.teaserInfo.picture || '';
topicData.teaser_timestamp = topicInfo.teaserInfo.timestamp ? utils.relativeTime(topicInfo.teaserInfo.timestamp) : '';
if (isTopicVisible(topicData, topicInfo))
retrieved_topics.push(topicData);
callback(null);
});
});
}
async.eachSeries(tids, loadTopic, function(err) {
if(!err) {
callback(retrieved_topics);
}
});
}
Categories.getAllCategories = function(callback, current_user) {
RDB.lrange('categories:cid', 0, -1, function(err, cids) {
RDB.handle(err);
@@ -258,7 +145,7 @@ var RDB = require('./redis.js'),
}
Categories.isTopicsRead = function(cid, uid, callback) {
RDB.smembers('categories:' + cid + ':tid', function(err, tids) {
RDB.zrange('categories:' + cid + ':tid', 0, -1, function(err, tids) {
topics.hasReadTopics(tids, uid, function(hasRead) {
@@ -269,22 +156,22 @@ var RDB = require('./redis.js'),
break;
}
}
callback(allread);
callback(allread);
});
});
}
Categories.markAsRead = function(cid, uid) {
RDB.sadd('cid:' + cid + ':read_by_uid', uid);
RDB.sadd('cid:' + cid + ':read_by_uid', uid);
}
Categories.hasReadCategories = function(cids, uid, callback) {
var batch = RDB.multi();
for (var i=0, ii=cids.length; i<ii; i++) {
batch.sismember('cid:' + cids[i] + ':read_by_uid', uid);
batch.sismember('cid:' + cids[i] + ':read_by_uid', uid);
}
batch.exec(function(err, hasRead) {
callback(hasRead);
});
@@ -293,24 +180,30 @@ var RDB = require('./redis.js'),
Categories.hasReadCategory = function(cid, uid, callback) {
RDB.sismember('cid:' + cid + ':read_by_uid', uid, function(err, hasRead) {
RDB.handle(err);
callback(hasRead);
});
});
}
Categories.getRecentReplies = function(cid, count, callback) {
RDB.zrevrange('categories:recent_posts:cid:' + cid, 0, (count<10)?10:count, function(err, pids) {
if(err) {
winston.err(err);
callback([]);
return;
}
if (pids.length == 0) {
callback([]);
return;
}
posts.getPostSummaryByPids(pids, function(posts) {
if(posts.length > count) {
posts = posts.slice(0, count);
posts.getPostSummaryByPids(pids, function(err, postData) {
if(postData.length > count) {
postData = postData.slice(0, count);
}
callback(posts);
callback(postData);
});
});
}
@@ -321,8 +214,8 @@ var RDB = require('./redis.js'),
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);
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
});
}
@@ -330,12 +223,12 @@ var RDB = require('./redis.js'),
if(!err) {
callback(null, 1)
} else {
console.log(err);
winston.err(err);
callback(err, null);
}
});
} else {
console.log(err);
winston.err(err);
callback(err, null);
}
});
@@ -347,16 +240,20 @@ var RDB = require('./redis.js'),
else callback(new Error('No category found!'));
});
}
Categories.getCategoryField = function(cid, field, callback) {
RDB.hget('category:' + cid, field, callback);
}
Categories.getCategoryFields = function(cid, fields, callback) {
RDB.hmgetObject('category:' + cid, fields, function(err, data) {
if(err === null)
if(err === null)
callback(data);
else
console.log(err);
});
winston.err(err);
});
}
Categories.setCategoryField = function(cid, field, value) {
RDB.hset('category:' + cid, field, value);
}
@@ -370,37 +267,37 @@ var RDB = require('./redis.js'),
callback({'categories' : []});
return;
}
var categories = [];
function getCategory(cid, callback) {
Categories.getCategoryData(cid, function(err, categoryData) {
if(err) {
callback(err);
return;
}
Categories.hasReadCategory(cid, current_user, function(hasRead) {
categoryData['badgeclass'] = (parseInt(categoryData.topic_count,10) === 0 || (hasRead && current_user != 0)) ? '' : 'badge-important';
categoryData['badgeclass'] = (parseInt(categoryData.topic_count, 10) === 0 || (hasRead && current_user != 0)) ? '' : 'badge-important';
categories.push(categoryData);
callback(null);
}) ;
});
});
}
async.eachSeries(cids, getCategory, function(err) {
if(err) {
console.log(err);
winston.err(err);
callback(null);
return;
}
callback({'categories': categories});
callback({'categories': categories});
});
};
};
}(exports));

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ var async = require('async'),
fs = require('fs'),
url = require('url'),
path = require('path'),
meta = require('./meta'),
install = {
questions: [
'base_url|Publically accessible URL of this installation? (http://localhost)',
@@ -84,6 +85,11 @@ var async = require('async'),
server_conf.base_url = protocol + '//' + host;
server_conf.relative_path = relative_path;
meta.configs.set('postDelay', 10000);
meta.configs.set('minimumPostLength', 8);
meta.configs.set('minimumTitleLength', 3);
meta.configs.set('imgurClientID', '');
install.save(server_conf, client_conf, callback);
});
},

View File

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

76
src/messaging.js Normal file
View File

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

View File

@@ -5,39 +5,34 @@ var utils = require('./../public/src/utils.js'),
fs = require('fs');
(function(Meta) {
Meta.config = {
Meta.configs = {
init: function(callback) {
Meta.configs.get(function(config) {
Meta.config = config;
callback();
});
},
get: function(callback) {
var config = {};
async.waterfall([
function(next) {
RDB.hkeys('config', function(err, keys) {
next(err, keys);
});
},
function(keys, next) {
async.each(keys, function(key, next) {
RDB.hget('config', key, function(err, value) {
if (!err) {
config[key] = value;
}
next(err);
});
}, next);
}
], function(err) {
RDB.hgetall('config', function(err, config) {
if (!err) {
config = config || {};
config.status = 'ok';
callback(config);
} else callback({
status: 'error'
});
} else {
callback({
status: 'error'
});
}
});
},
getFields: function(fields, callback) {
RDB.hmgetObject('config', fields, callback);
},
set: function(field, value, callback) {
RDB.hset('config', field, value, function(err, res) {
callback(err);
if(callback)
callback(err, res);
});
},
remove: function(field) {
@@ -60,13 +55,13 @@ var utils = require('./../public/src/utils.js'),
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';
conf.src = nconf.get('url') + 'themes/' + themeDir + '/' + conf.src;
if (conf.screenshot) conf.screenshot = nconf.get('url') + 'themes/' + themeDir + '/' + conf.screenshot;
else conf.screenshot = nconf.get('url') + 'images/themes/default.png';
themeArr.push(conf);
next();
});
}
} else next();
});
} else next();
});
@@ -74,23 +69,55 @@ var utils = require('./../public/src/utils.js'),
callback(err, themeArr);
});
});
},
saveViaGithub: function(repo_url, callback) {
// ...
}
}
Meta.build_title = function(title, current_user, callback) {
var user = require('./user');
Meta.title = {
build: function(urlFragment, current_user, callback) {
var self = this,
user = require('./user');
if (!title) title = global.config.title || 'NodeBB';
else title += ' | ' + global.config.title || 'NodeBB';
async.parallel({
title: function(next) {
self.parseFragment(urlFragment, next);
},
notifCount: function(next) {
user.notifications.getUnreadCount(current_user, next);
}
}, function(err, values) {
var title;
// Grab the number of unread notifications
user.notifications.getUnreadCount(current_user, function(err, count) {
if (!err && count > 0) title = '(' + count + ') ' + title;
if (err) title = Meta.config.title || 'NodeBB';
else title = (values.title ? values.title + ' | ' : '') + (Meta.config.title || 'NodeBB');
callback(err, title);
});
callback(null, title, values.notifCount);
});
},
parseFragment: function(urlFragment, callback) {
if (urlFragment === '') {
callback(null, 'Index');
} else if (urlFragment === 'recent') {
callback(null, 'Recent Topics');
} else if (urlFragment === 'unread') {
callback(null, 'Unread Topics');
} else if (urlFragment === 'users') {
callback(null, 'Registered Users');
} else if (/^category\/\d+\/?/.test(urlFragment)) {
var cid = urlFragment.match(/category\/(\d+)/)[1];
require('./categories').getCategoryField(cid, 'name', function(err, name) {
callback(null, name);
});
} else if (/^topic\/\d+\/?/.test(urlFragment)) {
var tid = urlFragment.match(/topic\/(\d+)/)[1];
require('./topics').getTopicField(tid, 'title', function(err, title) {
callback(null, title);
});
} else callback(null);
}
}
}(exports));
}(exports));

View File

@@ -1,121 +1,137 @@
var RDB = require('./redis.js'),
async = require('async'),
utils = require('../public/src/utils.js');
utils = require('../public/src/utils.js'),
(function(Notifications) {
Notifications.get = function(nid, callback) {
RDB.hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId', function(err, notification) {
callback({
nid: nid,
text: notification[0],
score: notification[1],
path: notification[2],
datetime: notification[3],
uniqueId: notification[4]
notifications = {
get: function(nid, callback) {
RDB.hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId', function(err, notification) {
callback({
nid: nid,
text: notification[0],
score: notification[1],
path: notification[2],
datetime: notification[3],
uniqueId: notification[4]
});
});
});
}
Notifications.create = function(text, score, path, uniqueId, callback) {
/*
* Score guide:
* 0 Low priority messages (probably unused)
* 5 Normal messages
* 10 High priority messages
*
* uniqueId is used solely to override stale nids.
* If a new nid is pushed to a user and an existing nid in the user's
* (un)read list contains the same uniqueId, it will be removed, and
* the new one put in its place.
*/
RDB.incr('notifications:next_nid', function(err, nid) {
RDB.hmset(
'notifications:' + nid,
'text', text || '',
'score', score || 5,
'path', path || null,
'datetime', Date.now(),
'uniqueId', uniqueId || utils.generateUUID(),
function(err, status) {
if (status === 'OK') callback(nid);
},
create: function(text, score, path, uniqueId, callback) {
/*
* Score guide:
* 0 Low priority messages (probably unused)
* 5 Normal messages
* 10 High priority messages
*
* uniqueId is used solely to override stale nids.
* If a new nid is pushed to a user and an existing nid in the user's
* (un)read list contains the same uniqueId, it will be removed, and
* the new one put in its place.
*/
RDB.incr('notifications:next_nid', function(err, nid) {
RDB.hmset(
'notifications:' + nid,
'text', text || '',
'score', score || 5,
'path', path || null,
'datetime', Date.now(),
'uniqueId', uniqueId || utils.generateUUID(),
function(err, status) {
if (status === 'OK') callback(nid);
});
});
});
}
},
push: function(nid, uids, callback) {
if (!Array.isArray(uids)) uids = [uids];
Notifications.push = function(nid, uids, callback) {
if (!Array.isArray(uids)) uids = [uids];
var numUids = uids.length,
x;
var numUids = uids.length,
x;
Notifications.get(nid, function(notif_data) {
for(x=0;x<numUids;x++) {
if (parseInt(uids[x]) > 0) {
(function(uid) {
Notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.score, nid);
RDB.set('uid:' + uid + ':notifications:flag', 1);
global.io.sockets.in('uid_' + uid).emit('event:new_notification');
if (callback) callback(true);
});
})(uids[x]);
notifications.get(nid, function(notif_data) {
for(x=0;x<numUids;x++) {
if (parseInt(uids[x]) > 0) {
(function(uid) {
notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.score, nid);
RDB.set('uid:' + uid + ':notifications:flag', 1);
global.io.sockets.in('uid_' + uid).emit('event:new_notification');
if (callback) callback(true);
});
})(uids[x]);
}
}
}
});
}
Notifications.remove_by_uniqueId = function(uniqueId, uid, callback) {
async.parallel([
function(next) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.get(nid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) RDB.zrem('uid:' + uid + ':notifications:unread', nid);
});
},
remove_by_uniqueId: function(uniqueId, uid, callback) {
async.parallel([
function(next) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
notifications.get(nid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) RDB.zrem('uid:' + uid + ':notifications:unread', nid);
next();
});
}, function(err) {
next();
});
}, function(err) {
next();
});
} else next();
});
},
function(next) {
RDB.zrange('uid:' + uid + ':notifications:read', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.get(nid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) RDB.zrem('uid:' + uid + ':notifications:read', nid);
} else next();
});
},
function(next) {
RDB.zrange('uid:' + uid + ':notifications:read', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
notifications.get(nid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) RDB.zrem('uid:' + uid + ':notifications:read', nid);
next();
});
}, function(err) {
next();
});
}, function(err) {
next();
});
} else next();
} else next();
});
}
], function(err) {
if (!err) callback(true);
});
},
mark_read: function(nid, uid, callback) {
if (parseInt(uid) > 0) {
notifications.get(nid, function(notif_data) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
RDB.zadd('uid:' + uid + ':notifications:read', notif_data.score, nid);
if (callback) callback();
});
}
], function(err) {
if (!err) callback(true);
});
}
},
mark_read_multiple: function(nids, uid, callback) {
if (!Array.isArray(nids) && parseInt(nids, 10) > 0) nids = [nids];
Notifications.mark_read = function(nid, uid, callback) {
if (parseInt(uid) > 0) {
Notifications.get(nid, function(notif_data) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
RDB.zadd('uid:' + uid + ':notifications:read', notif_data.score, nid);
if (callback) callback(true);
async.each(nids, function(nid, next) {
notifications.mark_read(nid, uid, function(err) {
if (!err) next(null);
});
}, function(err) {
if (callback) callback(err);
});
},
mark_all_read: function(uid, callback) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
if (err) return callback(err);
if (nids.length > 0) {
notifications.mark_read_multiple(nids, uid, function(err) {
callback(err);
});
} else callback();
});
}
}
Notifications.mark_read_multiple = function(nids, uid, callback) {
async.each(nids, function(nid, next) {
Notifications.mark_read(nid, uid, function(success) {
if (success) next(null);
});
}, function(err) {
if (callback && !err) callback(true);
});
}
}(exports));
module.exports = {
get: notifications.get,
create: notifications.create,
push: notifications.push,
mark_read: notifications.mark_read_multiple,
mark_all_read: notifications.mark_all_read
}

233
src/plugins.js Normal file
View File

@@ -0,0 +1,233 @@
var fs = require('fs'),
path = require('path'),
RDB = require('./redis.js'),
async = require('async'),
winston = require('winston'),
plugins = {
libraries: [],
loadedHooks: {},
init: function() {
if (this.initialized) return;
if (global.env === 'development') winston.info('[plugins] Initializing plugins system');
var _self = this;
// Read the list of activated plugins and require their libraries
async.waterfall([
function(next) {
RDB.smembers('plugins:active', next);
},
function(plugins, next) {
async.each(plugins, function(plugin) {
// TODO: Update this check to also check node_modules
var pluginPath = path.join(__dirname, '../plugins/', plugin),
modulePath = path.join(__dirname, '../node_modules/', plugin);
if (fs.existsSync(pluginPath)) _self.loadPlugin(pluginPath, next);
else if (fs.existsSync(modulePath)) _self.loadPlugin(modulePath, next);
else {
if (global.env === 'development') winston.info('[plugins] Plugin \'' + plugin + '\' not found');
next(); // Ignore this plugin silently
}
}, next);
}
], function(err) {
if (err) {
if (global.env === 'development') winston.info('[plugins] NodeBB encountered a problem while loading plugins', err.message);
return;
}
if (global.env === 'development') winston.info('[plugins] Plugins OK');
});
},
initialized: false,
loadPlugin: function(pluginPath, callback) {
var _self = this;
fs.readFile(path.join(pluginPath, 'plugin.json'), function(err, data) {
if (err) return callback(err);
var pluginData = JSON.parse(data);
_self.libraries[pluginData.id] = require(path.join(pluginPath, pluginData.library));
if (pluginData.hooks) {
for(var x=0,numHooks=pluginData.hooks.length;x<numHooks;x++) {
_self.registerHook(pluginData.id, pluginData.hooks[x]);
}
}
if (global.env === 'development') winston.info('[plugins] Loaded plugin: ' + pluginData.id);
callback();
});
},
registerHook: function(id, data) {
/*
`data` is an object consisting of (* is required):
`data.hook`*, the name of the NodeBB hook
`data.method`*, the method called in that plugin
`data.callbacked`, whether or not the hook expects a callback (true), or a return (false). Only used for filters. (Default: false)
(Not implemented) `data.priority`, the relative priority of the method when it is eventually called (default: 10)
*/
var _self = this;
if (data.hook && data.method) {
_self.loadedHooks[data.hook] = _self.loadedHooks[data.hook] || [];
_self.loadedHooks[data.hook].push([id, data.method, !!data.callbacked]);
if (global.env === 'development') winston.info('[plugins] Hook registered: ' + data.hook + ' will call ' + id);
} else return;
},
fireHook: function(hook, args, callback) {
// TODO: Implement priority hook firing
var _self = this
hookList = this.loadedHooks[hook];
if (hookList && Array.isArray(hookList)) {
if (global.env === 'development') winston.info('[plugins] Firing hook: \'' + hook + '\'');
var hookType = hook.split(':')[0];
switch(hookType) {
case 'filter':
// Filters only take one argument, so only args[0] will be passed in
var returnVal = (Array.isArray(args) ? args[0] : args);
async.each(hookList, function(hookObj, next) {
if (hookObj[2]) {
_self.libraries[hookObj[0]][hookObj[1]](returnVal, function(err, afterVal) {
returnVal = afterVal;
next(err);
});
} else {
returnVal = _self.libraries[hookObj[0]][hookObj[1]](returnVal);
next();
}
}, function(err) {
if (err) {
if (global.env === 'development') {
winston.info('[plugins] Problem executing hook: ' + hook);
}
}
callback(returnVal);
});
break;
case 'action':
async.each(hookList, function(hookObj) {
if (
_self.libraries[hookObj[0]] &&
_self.libraries[hookObj[0]][hookObj[1]] &&
typeof _self.libraries[hookObj[0]][hookObj[1]] === 'function'
) {
_self.libraries[hookObj[0]][hookObj[1]].apply(_self.libraries[hookObj[0]], args);
} else {
if (global.env === 'development') winston.info('[plugins] Expected method \'' + hookObj[1] + '\' in plugin \'' + hookObj[0] + '\' not found, skipping.');
}
});
break;
default:
// Do nothing...
break;
}
} else {
// Otherwise, this hook contains no methods
var returnVal = (Array.isArray(args) ? args[0] : args);
if (callback) callback(returnVal);
}
},
isActive: function(id, callback) {
RDB.sismember('plugins:active', id, callback);
},
toggleActive: function(id, callback) {
this.isActive(id, function(err, active) {
if (err) {
if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return;
}
RDB[(active ? 'srem' : 'sadd')]('plugins:active', id, function(err, success) {
if (err) {
if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return;
}
callback({
id: id,
active: !active
});
});
});
},
showInstalled: function(callback) {
// TODO: Also check /node_modules
var _self = this;
localPluginPath = path.join(__dirname, '../plugins'),
npmPluginPath = path.join(__dirname, '../node_modules');
async.waterfall([
function(next) {
async.parallel([
function(next) {
fs.readdir(localPluginPath, next);
},
function(next) {
fs.readdir(npmPluginPath, next);
}
], function(err, dirs) {
if (err) return next(err);
dirs[0] = dirs[0].map(function(file) {
return path.join(localPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory()) return true;
else return false;
});
dirs[1] = dirs[1].map(function(file) {
return path.join(npmPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory() && file.substr(npmPluginPath.length+1, 14) === 'nodebb-plugin-') return true;
else return false;
});
next(err, dirs[0].concat(dirs[1]));
});
},
function(files, next) {
var plugins = [];
async.each(files, function(file, next) {
var configPath;
async.waterfall([
function(next) {
fs.readFile(path.join(file, 'plugin.json'), next);
},
function(configJSON, next) {
var config = JSON.parse(configJSON);
_self.isActive(config.id, function(err, active) {
if (err) next(new Error('no-active-state'));
delete config.library;
delete config.hooks;
config.active = active;
config.activeText = '<i class="icon-off"></i> ' + (active ? 'Dea' : 'A') + 'ctivate';
next(null, config);
});
}
], function(err, config) {
if (err) return next(); // Silently fail
plugins.push(config);
next();
});
}, function(err) {
next(null, plugins);
});
}
], function(err, plugins) {
callback(err, plugins);
});
}
}
plugins.init();
module.exports = plugins;

View File

@@ -4,8 +4,14 @@ var RDB = require('./redis.js'),
threadTools = require('./threadTools.js'),
user = require('./user.js'),
async = require('async'),
utils = require('../public/src/utils');
utils = require('../public/src/utils'),
plugins = require('./plugins'),
reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
meta = require('./meta.js');
(function(PostTools) {
PostTools.isMain = function(pid, tid, callback) {
@@ -16,8 +22,8 @@ var RDB = require('./redis.js'),
}
PostTools.privileges = function(pid, uid, callback) {
//todo: break early if one condition is true
//todo: break early if one condition is true
function getThreadPrivileges(next) {
posts.getPostField(pid, 'tid', function(tid) {
threadTools.privileges(tid, uid, function(privileges) {
@@ -35,8 +41,9 @@ var RDB = require('./redis.js'),
}
function hasEnoughRep(next) {
user.getUserField(uid, 'reputation', function(reputation) {
next(null, reputation >= global.config['privileges:manage_content']);
user.getUserField(uid, 'reputation', function(err, reputation) {
if (err) return next(null, false);
next(null, reputation >= meta.config['privileges:manage_content']);
});
}
@@ -55,10 +62,18 @@ var RDB = require('./redis.js'),
posts.setPostField(pid, 'edited', Date.now());
posts.setPostField(pid, 'editor', uid);
postSearch.remove(pid, function() {
postSearch.index(content, pid);
});
posts.getPostField(pid, 'tid', function(tid) {
PostTools.isMain(pid, tid, function(isMainPost) {
if (isMainPost)
if (isMainPost) {
topics.setTopicField(tid, 'title', title);
topicSearch.remove(tid, function() {
topicSearch.index(title, tid);
});
}
io.sockets.in('topic_' + tid).emit('event:post_edited', {
pid: pid,
@@ -71,7 +86,10 @@ var RDB = require('./redis.js'),
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
plugins.fireHook('filter:save_post_content', content, function(parsedContent) {
content = parsedContent;
success();
});
}
});
}
@@ -79,12 +97,15 @@ var RDB = require('./redis.js'),
PostTools.delete = function(uid, pid) {
var success = function() {
posts.setPostField(pid, 'deleted', 1);
postSearch.remove(pid);
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
user.decrementUserFieldBy(postData.uid, 'postcount', 1);
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
RDB.zadd('users:postcount', postcount, postData.uid);
});
io.sockets.in('topic_' + postData.tid).emit('event:post_deleted', {
pid: pid
});
@@ -93,12 +114,12 @@ var RDB = require('./redis.js'),
threadTools.get_latest_undeleted_pid(postData.tid, function(err, pid) {
if (err && err.message === 'no-undeleted-pids-found') {
threadTools.delete(postData.tid, -1, function(err) {
if (err) console.log('Error: Could not delete topic (tid: ' + postData.tid + ')');
if (err) winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
});
} else {
posts.getPostField(pid, 'timestamp', function(timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
topics.updateTimestamp(postData.tid, timestamp);
});
}
});
});
@@ -115,19 +136,21 @@ var RDB = require('./redis.js'),
var success = function() {
posts.setPostField(pid, 'deleted', 0);
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
posts.getPostFields(pid, ['tid', 'uid', 'content'], 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);
});
});
});
postSearch.index(postData.content, pid);
});
};
@@ -148,18 +171,18 @@ var RDB = require('./redis.js'),
if (md && md.length > 0) {
var parsedContentDOM = cheerio.load(marked(md));
var domain = global.nconf.get('url');
var domain = 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 (href && !href.match(domain) && !utils.isRelativeUrl(href)) {
this.attr('href', domain + 'outgoing?url=' + encodeURIComponent(href));
if (!isSignature) this.append(' <i class="icon-external-link"></i>');
}
});
html = parsedContentDOM.html();
} else {

View File

@@ -7,43 +7,50 @@ var RDB = require('./redis.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools'),
feed = require('./feed.js'),
async = require('async');
async = require('async'),
plugins = require('./plugins'),
reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'),
meta = require('./meta.js'),
winston = require('winston');
(function(Posts) {
Posts.minimumPostLength = 8;
Posts.getPostsByTid = function(tid, start, end, callback) {
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
RDB.handle(err);
if (pids.length) {
Posts.getPostsByPids(pids, function(posts) {
Posts.getPostsByPids(pids, function(err, posts) {
callback(posts);
});
} else {
callback({
error: 'no-posts'
});
callback([]);
}
});
}
Posts.addUserInfoToPost = function(post, callback) {
user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature'], function(userData) {
user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], function(err, userData) {
if(err)
return callback();
post.username = userData.username || 'anonymous';
post.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.user_banned = userData.banned || '0';
post.picture = userData.picture || require('gravatar').url('', {}, https=nconf.get('https'));
post.signature = postTools.markdownToHTML(userData.signature, true);
if(post.editor !== '') {
user.getUserFields(post.editor, ['username', 'userslug'], function(editorData) {
user.getUserFields(post.editor, ['username', 'userslug'], function(err, editorData) {
if(err)
return callback();
post.editorname = editorData.username;
post.editorslug = editorData.userslug;
post.editorslug = editorData.userslug;
callback();
});
} else {
@@ -53,33 +60,56 @@ var RDB = require('./redis.js'),
}
Posts.getPostSummaryByPids = function(pids, callback) {
var returnData = [];
var posts = [];
function getPostSummary(pid, callback) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
Posts.addUserInfoToPost(postData, function() {
if(postData.deleted === '1') {
return callback(null);
}
if(postData.deleted !== '1') {
returnData.push(postData);
}
callback(null);
Posts.addUserInfoToPost(postData, function() {
topics.getTopicFields(postData.tid, ['slug', 'deleted'], function(err, topicData) {
if(err)
return callback(err);
if(topicData.deleted === '1')
return callback(null);
if(postData.content)
postData.content = utils.strip_tags(postTools.markdownToHTML(postData.content));
postData.relativeTime = utils.relativeTime(postData.timestamp);
postData.topicSlug = topicData.slug;
posts.push(postData);
callback(null);
});
});
});
}
async.eachSeries(pids, getPostSummary, function(err) {
if(!err) {
callback(returnData);
callback(null, posts);
} else {
callback(err, null);
}
});
};
Posts.filterBannedPosts = function(posts) {
return posts.filter(function(post) {
return post.user_banned === '0';
});
}
Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) {
if(err === null) {
callback(data);
plugins.fireHook('filter:post.get', data, function(data) {
callback(data);
});
}
else
console.log(err);
@@ -94,39 +124,6 @@ var RDB = require('./redis.js'),
else {
console.log(err);
}
});
}
Posts.getPostsByPids = function(pids, callback) {
var posts = [];
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) : '';
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([]);
}
});
}
@@ -143,26 +140,49 @@ var RDB = require('./redis.js'),
RDB.hset('post:' + pid, field, value);
}
Posts.getPostFields = function(pid, fields, callback) {
RDB.hmget('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);
Posts.getPostsByPids = function(pids, callback) {
var posts = [];
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) : '';
postData.content = postTools.markdownToHTML(postData.content);
if(postData.uploadedImages) {
try {
postData.uploadedImages = JSON.parse(postData.uploadedImages);
} catch(err) {
postData.uploadedImages = [];
winston.err(err);
}
} else {
postData.uploadedImages = [];
}
posts.push(postData);
}
callback(null);
});
}
async.eachSeries(pids, iterator, function(err) {
if(!err) {
callback(null, posts);
} else {
callback(err, null);
}
else
console.log(err);
});
});
}
Posts.get_cid_by_pid = function(pid, callback) {
Posts.getPostField(pid, 'tid', function(tid) {
if (tid) {
topics.getTopicField(tid, 'cid', function(cid) {
topics.getTopicField(tid, 'cid', function(err, cid) {
if (cid) {
callback(cid);
} else {
@@ -178,208 +198,209 @@ var RDB = require('./redis.js'),
type: 'error',
timeout: 2000,
title: 'Content too short',
message: "Please enter a longer post. At least " + Posts.minimumPostLength + " characters.",
message: "Please enter a longer post. At least " + meta.config.minimumPostLength + " characters.",
alert_id: 'post_error'
});
}
Posts.reply = function(socket, tid, uid, content, images) {
Posts.emitTooManyPostsAlert = function(socket) {
socket.emit('event:alert', {
title: 'Too many posts!',
message: 'You can only post every '+ meta.config.postDelay/1000 + ' seconds.',
type: 'error',
timeout: 2000
});
}
Posts.reply = function(tid, uid, content, images, callback) {
if(content) {
content = content.trim();
}
if (uid < 1) {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'You don&apos;t seem to be logged in, so you cannot reply.',
type: 'error',
timeout: 2000
});
return;
} else if (!content || content.length < Posts.minimumPostLength) {
Posts.emitContentTooShortAlert(socket);
if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(Date.now() - lastposttime < config.post_delay) {
socket.emit('event:alert', {
title: 'Too many posts!',
message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.',
type: 'error',
timeout: 2000
});
if(Date.now() - lastposttime < meta.config.postDelay) {
callback(new Error('too-many-posts'), null);
return;
}
Posts.create(uid, tid, content, images, function(postData) {
if (postData) {
topics.addPostToTopic(tid, postData.pid);
topics.markUnRead(tid);
Posts.get_cid_by_pid(postData.pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
topics.markAsRead(tid, uid);
topics.markAsRead(tid, uid);
});
});
Posts.getTopicPostStats(socket);
// 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.',
type: 'notify',
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 socketData = {
'posts' : [
postData
]
};
posts.addUserInfoToPost(socketData['posts'][0], function() {
Posts.addUserInfoToPost(postData, function() {
var socketData = { posts: [postData] };
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData);
});
} else {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'Your reply could not be posted at this time. Please try again later.',
type: 'notify',
timeout: 2000
});
callback(null, 'Reply successful');
} else {
callback(new Error('reply-error'), null);
}
});
});
};
Posts.create = function(uid, tid, content, images, callback) {
if (uid === null) {
callback(null);
return;
}
topics.isLocked(tid, function(locked) {
topics.isLocked(tid, function(locked) {
if (!locked || locked === '0') {
RDB.incr('global:next_post_id', function(err, pid) {
RDB.handle(err);
var timestamp = Date.now();
var postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': content,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0,
'uploadedImages': ''
};
RDB.hmset('post:' + pid, postData);
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
plugins.fireHook('filter:post.save', content, function(content) {
var timestamp = Date.now(),
postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': content,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0,
'uploadedImages': '[]',
'fav_button_class': '',
'fav_star_class': 'icon-star-empty',
'show_banned': 'hide',
'relativeTime': '0 seconds',
'post_rep': '0',
'edited-class': 'none',
'relativeEditTime': ''
};
RDB.incr('totalpostcount');
topics.getTopicField(tid, 'cid', function(cid) {
RDB.handle(err);
RDB.hmset('post:' + pid, postData);
feed.updateTopic(tid, cid);
topics.addPostToTopic(tid, pid);
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
RDB.zadd('categories:recent_posts:cid:' + cid, Date.now(), pid);
RDB.incr('totalpostcount');
// this is a bit of a naive implementation, defn something to look at post-MVP
RDB.scard('cid:' + cid + ':active_users', function(amount) {
if (amount > 10) {
RDB.spop('cid:' + cid + ':active_users');
}
topics.getTopicField(tid, 'cid', function(err, cid) {
RDB.handle(err);
RDB.sadd('cid:' + cid + ':active_users', uid);
});
});
user.onNewPostMade(uid, tid, pid, timestamp);
feed.updateTopic(tid, cid);
var imgur = require('./imgur');
// move clientID to config
imgur.setClientID('09f3955fee9a0a6');
var uploadedImages = [];
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
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);
// this is a bit of a naive implementation, defn something to look at post-MVP
RDB.scard('cid:' + cid + ':active_users', function(amount) {
if (amount > 10) {
RDB.spop('cid:' + cid + ':active_users');
}
}
});
}
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);
}
RDB.sadd('cid:' + cid + ':active_users', uid);
});
});
}
user.onNewPostMade(uid, tid, pid, timestamp);
async.parallel({
uploadedImages: function(next) {
uploadPostImages(postData, images, function(err, uploadedImages) {
if(err) {
winston.error('Uploading images failed!', err.stack);
next(null, []);
} else {
next(null, uploadedImages);
}
});
},
content: function(next) {
plugins.fireHook('filter:post.get', postData, function(postData) {
postData.content = postTools.markdownToHTML(postData.content, false);
next(null, postData.content);
});
}
}, function(err, results) {
postData.uploadedImages = results.uploadedImages;
Posts.setPostField(pid, 'uploadedImages', JSON.stringify(postData.uploadedImages));
postData.content = results.content;
callback(postData);
});
plugins.fireHook('action:post.save', [postData]);
postSearch.index(content, pid);
});
});
} else {
callback(null);
}
});
}
function uploadPostImages(postData, images, callback) {
var imgur = require('./imgur');
imgur.setClientID(meta.config.imgurClientID);
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 {
winston.error('Can\'t upload image, did you set imgurClientID?');
callback(data);
}
}
});
}
if(!images) {
callback(null, uploadedImages);
} else {
async.each(images, uploadImage, function(err) {
if(!err) {
callback(null, uploadedImages);
} else {
console.log(err);
callback(err, null);
}
});
}
}
Posts.getPostsByUid = function(uid, start, end, callback) {
user.getPostIds(uid, start, end, function(pids) {
if(pids && pids.length) {
Posts.getPostsByPids(pids, function(posts) {
Posts.getPostsByPids(pids, function(err, posts) {
callback(posts);
});
}
else
callback([]);
});
});
}
Posts.getTopicPostStats = function(socket) {
@@ -387,14 +408,53 @@ var RDB = require('./redis.js'),
if(err === null) {
var stats = {
topics: data[0]?data[0]:0,
posts: data[1]?data[1]:0
posts: data[1]?data[1]:0
};
socket.emit('post.stats', stats);
}
}
else
console.log(err);
});
}
Posts.reIndexPids = function(pids, callback) {
function reIndex(pid, callback) {
Posts.getPostField(pid, 'content', function(content) {
postSearch.remove(pid, function() {
if(content && content.length) {
postSearch.index(content, pid);
}
callback(null);
});
});
}
async.each(pids, reIndex, function(err) {
if(err) {
callback(err, null);
} else {
callback(null, 'Posts reindexed');
}
});
}
Posts.getFavourites = function(uid, callback) {
RDB.zrevrange('uid:' + uid + ':favourites', 0, -1, function(err, pids) {
if(err)
return callback(err, null);
Posts.getPostSummaryByPids(pids, function(err, posts) {
if(err)
return callback(err, null);
callback(null, posts);
});
});
}
}(exports));

View File

@@ -1,23 +1,20 @@
(function(RedisDB) {
var redis = require('redis'),
utils = require('./../public/src/utils.js');
RedisDB.exports = redis.createClient(global.nconf.get('redis:port'), global.nconf.get('redis:host'));
var redis = require('redis'),
nconf = require('nconf'),
utils = require('./../public/src/utils.js'),
winston = require('winston');
if( global.nconf.get('redis:password') ) {
RedisDB.exports.auth(global.nconf.get('redis:password'));
RedisDB.exports = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'));
if(nconf.get('redis:password')) {
RedisDB.exports.auth(nconf.get('redis:password'));
}
RedisDB.exports.handle = function(error) {
if (error !== null) {
winston.err(error);
if (global.env !== 'production') {
console.log("################# ERROR LOG ####################");
console.log(error);
console.log(arguments.callee.name);
console.log("################# ERROR LOG ####################");
throw new Error(error);
} else {
console.log(error);
}
}
}
@@ -43,7 +40,7 @@
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];
}
@@ -54,8 +51,8 @@
console.log(err);
callback(err, null);
}
});
}
});
}

View File

@@ -3,7 +3,9 @@ var user = require('./../user.js'),
topics = require('./../topics.js'),
RDB = require('./../redis.js'),
pkg = require('./../../package.json'),
categories = require('./../categories.js');
categories = require('./../categories.js'),
plugins = require('../plugins'),
winston = require('winston');
(function(Admin) {
Admin.isAdmin = function(req, res, next) {
@@ -16,15 +18,19 @@ var user = require('./../user.js'),
Admin.build_header = function(res) {
return templates['admin/header'].parse({
csrf:res.locals.csrf_token,
relative_path: global.nconf.get('relative_path')
relative_path: nconf.get('relative_path')
});
}
Admin.create_routes = function(app) {
(function() {
var routes = ['categories', 'users', 'topics', 'settings', 'themes', 'twitter', 'facebook', 'gplus', 'redis', 'motd',
'users/latest', 'users/sort-posts', 'users/sort-reputation', 'users/search'];
var routes = [
'categories/active', 'categories/disabled', 'users', 'topics', 'settings', 'themes',
'twitter', 'facebook', 'gplus', 'redis', 'motd',
'users/latest', 'users/sort-posts', 'users/sort-reputation',
'users/search', 'plugins'
];
for (var i=0, ii=routes.length; i<ii; i++) {
(function(route) {
@@ -46,7 +52,6 @@ var user = require('./../user.js'),
}());
//todo consolidate.
app.get('/admin', Admin.isAdmin, function(req, res) {
res.send(Admin.build_header(res) + app.create_route('admin/index') + templates['admin/footer']);
});
@@ -55,97 +60,134 @@ var user = require('./../user.js'),
res.send(Admin.build_header(res) + app.create_route('admin/index') + templates['admin/footer']);
});
app.get('/api/admin/index', function(req, res) {
res.json({version:pkg.version});
});
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: []});
}
else if(req.params.tab == 'latest') {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.joindate - a.joindate;
});
res.json({search_display: 'none', users:data, yourid:req.user.uid});
});
}
else if(req.params.tab == 'sort-posts') {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.postcount - a.postcount;
});
res.json({search_display: 'none', users:data, yourid:req.user.uid});
});
}
else if(req.params.tab == 'sort-reputation') {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.reputation - a.reputation;
});
res.json({search_display: 'none', users:data, yourid:req.user.uid});
});
}
else {
user.getUserList(function(data) {
res.json({search_display: 'none', users:data, yourid:req.user.uid});
});
}
app.get('/api/admin/users/search', function(req, res) {
res.json({search_display: 'block', loadmore_display:'none', users: []});
});
app.get('/api/admin/users/latest', function(req, res) {
user.getUsers('users:joindate', 0, 49, function(err, data) {
res.json({ search_display: 'none', loadmore_display:'block', users:data, yourid:req.user.uid });
});
});
app.get('/api/admin/users/sort-posts', function(req, res) {
user.getUsers('users:postcount', 0, 49, function(err, data) {
res.json({ search_display: 'none', loadmore_display:'block', users:data, yourid:req.user.uid });
});
});
app.get('/api/admin/users/sort-reputation', function(req, res) {
user.getUsers('users:reputation', 0, 49, function(err, data) {
res.json({ search_display: 'none', loadmore_display:'block', users:data, yourid:req.user.uid });
});
});
app.get('/api/admin/users', function(req, res) {
user.getUsers('users:joindate', 0, 49, function(err, data) {
res.json({ search_display: 'none', users:data, yourid:req.user.uid });
});
});
app.get('/api/admin/categories', function(req, res) {
categories.getAllCategories(function(data) {
res.json(data);
});
});
app.get('/api/admin/categories/active', function(req, res) {
categories.getAllCategories(function(data) {
data.categories = data.categories.filter(function(category) {
return (!category.disabled || category.disabled === "0");
});
res.json(data);
});
});
app.get('/api/admin/categories/disabled', function(req, res) {
categories.getAllCategories(function(data) {
data.categories = data.categories.filter(function(category) {
return category.disabled === "1";
});
res.json(data);
});
});
app.get('/api/admin/topics', function(req, res) {
topics.getAllTopics(10, null, function(topics) {
res.json({
topics: topics
});
});
});
app.get('/api/admin/redis', function(req, res) {
RDB.info(function(err, data) {
data = data.split("\r\n");
var finalData = {};
for(var i in data) {
break;
case 'categories':
if (req.params.tab == 'disabled') {
res.json({categories: []});
} else {
categories.getAllCategories(function(data) {
res.json(data);
});
}
break;
case 'topics':
topics.getAllTopics(10, null, function(topics) {
res.json({
topics: topics
});
});
break;
case 'redis':
RDB.info(function(err, data) {
data = data.split("\r\n");
var finalData = {};
for(var i in data) {
try {
data[i] = data[i].replace(/:/,"\":\"");
var json = "{\"" + data[i] + "\"}";
var jsonObject = JSON.parse(json);
for(var key in jsonObject) {
finalData[key] = jsonObject[key];
}
}catch(err){
}
}
if(data[i].indexOf(':') == -1 || !data[i])
continue;
try {
data[i] = data[i].replace(/:/,"\":\"");
var json = "{\"" + data[i] + "\"}";
res.json(finalData);
});
break;
default :
res.json({});
}
}
var jsonObject = JSON.parse(json);
for(var key in jsonObject) {
finalData[key] = jsonObject[key];
}
} catch(err){
winston.warn('can\'t parse redis status variable, ignoring', i, data[i], err);
}
}
res.json(finalData);
});
});
app.get('/api/admin/:method/:tab?*', api_method);
app.get('/api/admin/:method*', api_method);
app.get('/api/admin/plugins', function(req, res) {
plugins.showInstalled(function(err, plugins) {
if (err || !Array.isArray(plugins)) plugins = [];
res.json(200, {
plugins: plugins
});
});
});
app.get('/api/admin/settings', function(req, res) {
res.json(200, {});
});
app.get('/api/admin/motd', function(req, res) {
res.json(200, {});
});
app.get('/api/admin/themes', function(req, res) {
res.json(200, {});
});
app.get('/api/admin/twitter', function(req, res) {
res.json(200, {});
});
app.get('/api/admin/facebook', function(req, res) {
res.json(200, {});
});
app.get('/api/admin/gplus', function(req, res) {
res.json(200, {});
});
app.get('/api/admin/testing/categories', function(req, res) {
res.json(200, {});
});
};

250
src/routes/api.js Normal file
View File

@@ -0,0 +1,250 @@
var user = require('./../user.js'),
auth = require('./authentication.js'),
topics = require('./../topics.js'),
categories = require('./../categories.js')
utils = require('./../../public/src/utils.js'),
pkg = require('../../package.json'),
meta = require('./../meta.js');
(function(Api) {
Api.create_routes = function(app) {
app.get('/api/get_templates_listing', function(req, res) {
utils.walk(global.configuration.ROOT_DIRECTORY + '/public/templates', function(err, data) {
res.json(data);
});
});
app.get('/api/config', function(req, res, next) {
var config = require('../../public/config.json');
config['postDelay'] = meta.config['postDelay'];
config['minimumTitleLength'] = meta.config['minimumTitleLength'];
config['minimumPostLength'] = meta.config['minimumPostLength'];
config['imgurClientIDSet'] = !!meta.config['imgurClientID'];
res.json(200, config);
});
app.get('/api/home', function(req, res) {
var uid = (req.user) ? req.user.uid : 0;
categories.getAllCategories(function(data) {
data.categories = data.categories.filter(function(category) {
return (!category.disabled || category.disabled === "0");
});
function iterator(category, callback) {
categories.getRecentReplies(category.cid, 2, function(posts) {
category["posts"] = posts;
category["post_count"] = posts.length>2 ? 2 : posts.length;
callback(null);
});
}
require('async').each(data.categories, iterator, function(err) {
data.motd_class = (meta.config.show_motd === '1' || meta.config.show_motd === undefined) ? '' : 'none';
data.motd = marked(meta.config.motd || "# NodeBB <span class='hidden-phone'>v " + pkg.version + "</span>\nWelcome to NodeBB, the discussion platform of the future.\n\n<a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-large\"><i class=\"icon-comment\"></i><span class='hidden-phone'>&nbsp;Get NodeBB</span></a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-large\"><i class=\"icon-github-alt\"></i><span class='hidden-phone'>&nbsp;Fork us on Github</span></a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-large\"><i class=\"icon-twitter\"></i><span class='hidden-phone'>&nbsp;@dcplabs</span></a>");
res.json(data);
});
}, uid);
});
app.get('/api/login', function(req, res) {
var data = {},
login_strategies = auth.get_login_strategies(),
num_strategies = login_strategies.length;
if (num_strategies == 0) {
data = {
'login_window:spansize': 'span12',
'alternate_logins:display': 'none'
};
} else {
data = {
'login_window:spansize': 'span6',
'alternate_logins:display': 'block'
}
for (var i=0, ii=num_strategies; i<ii; i++) {
data[login_strategies[i] + ':display'] = 'active';
}
}
data.token = res.locals.csrf_token;
res.json(data);
});
app.get('/api/register', function(req, res) {
var data = {},
login_strategies = auth.get_login_strategies(),
num_strategies = login_strategies.length;
if (num_strategies == 0) {
data = {
'register_window:spansize': 'span12',
'alternate_logins:display': 'none'
};
} else {
data = {
'register_window:spansize': 'span6',
'alternate_logins:display': 'block'
}
for (var i=0, ii=num_strategies; i<ii; i++) {
data[login_strategies[i] + ':display'] = 'active';
}
}
data.token = res.locals.csrf_token;
res.json(data);
});
app.get('/api/topic/:id/:slug?', function(req, res, next) {
var uid = (req.user) ? req.user.uid : 0;
topics.getTopicWithPosts(req.params.id, uid, function(err, data) {
if(data.deleted === '1' && data.expose_tools === 0) {
return res.json(404, {});
}
res.json(data);
});
});
app.get('/api/category/:id/:slug?', function(req, res, next) {
var uid = (req.user) ? req.user.uid : 0;
categories.getCategoryById(req.params.id, uid, function(err, data) {
if (!err)
res.json(data);
else
next();
}, req.params.id, uid);
});
app.get('/api/recent', function(req, res) {
var uid = (req.user) ? req.user.uid : 0;
topics.getLatestTopics(uid, 0, 19, function(data) {
res.json(data);
});
});
app.get('/api/unread', function(req, res) {
var uid = (req.user) ? req.user.uid : 0;
topics.getUnreadTopics(uid, 0, 19, function(data) {
res.json(data);
});
});
app.get('/api/unread/total', function(req, res) {
var uid = (req.user) ? req.user.uid : 0;
topics.getTotalUnread(uid, function(data) {
res.json(data);
});
});
app.get('/api/confirm/:id', function(req, res) {
user.email.confirm(req.params.id, function(data) {
if (data.status === 'ok') {
res.json({
'alert-class': 'alert-success',
title: 'Email Confirmed',
text: 'Thank you for vaidating your email. Your account is now fully activated.'
});
} else {
res.json({
'alert-class': 'alert-error',
title: 'An error occurred...',
text: 'There was a problem validating your email address. Perhaps the code was invalid or has expired.'
});
}
});
});
app.get('/api/outgoing', function(req, res) {
var url = req.query.url;
if (url) {
res.json({
url: url,
home: nconf.get('url')
});
} else {
res.status(404);
res.redirect(nconf.get('relative_path') + '/404');
}
});
app.get('/api/search', function(req, res) {
return res.json({
show_no_results:'hide',
search_query:'',
posts:[]
});
});
app.get('/api/search/:term', function(req, res, next) {
var reds = require('reds');
var postSearch = reds.createSearch('nodebbpostsearch');
var topicSearch = reds.createSearch('nodebbtopicsearch');
function search(searchObj, callback) {
searchObj
.query(query = req.params.term).type('or')
.end(callback);
}
function searchPosts(callback) {
search(postSearch, function(err, pids) {
if(err)
return callback(err, null);
posts.getPostSummaryByPids(pids, function(err, posts) {
if(err)
return callback(err, null);
callback(null, posts);
});
})
}
function searchTopics(callback) {
search(topicSearch, function(err, tids) {
if(err)
return callback(err, null);
topics.getTopicsByTids(tids, 0, function(topics) {
callback(null, topics);
}, 0);
});
}
async.parallel([searchPosts, searchTopics], function(err, results) {
if (err)
return next();
var noresults = !results[0].length && !results[1].length;
return res.json({
show_no_results: noresults?'show':'hide',
search_query:req.params.term,
posts:results[0],
topics:results[1]
});
});
});
app.get('/api/reset', function(req, res) {
res.json({});
});
app.get('/api/reset/:code', function(req, res) {
res.json({ reset_code: req.params.code });
});
app.get('/api/404', function(req, res) {
res.json({});
});
app.get('/api/403', function(req, res) {
res.json({});
});
}
}(exports));

View File

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

View File

@@ -24,20 +24,13 @@ var RDB = require('../redis.js');
res.send(templates['install/header'] + app.create_route('install/basic') + templates['install/footer']);
});
app.get('/api/install/basic', function(req, res) {
res.send('{}');
});
function api_method(req, res) {
switch(req.params.method) {
case 'basic' :
res.send('{}');
break;
default :
res.send('{}');
}
}
app.get('/api/install/:method/:tab?*', api_method);
app.get('/api/install/:method*', api_method);
app.get('/api/install', function(req, res) {
res.send('{}');
});
};

View File

@@ -4,16 +4,17 @@ var user = require('./../user.js'),
fs = require('fs'),
utils = require('./../../public/src/utils.js'),
path = require('path'),
marked = require('marked');
marked = require('marked'),
winston = require('winston');
(function(User) {
User.create_routes = function(app) {
app.get('/uid/:uid', function(req, res) {
if(!req.params.uid)
return res.redirect('/404');
user.getUserData(req.params.uid, function(data) {
if(data) {
res.send(data);
@@ -21,7 +22,7 @@ var user = require('./../user.js'),
res.json(404, {error:"User doesn't exist!"});
}
});
});
app.get('/users', function(req, res) {
@@ -29,25 +30,25 @@ var user = require('./../user.js'),
res.send(header + app.create_route("users", "users") + templates['footer']);
});
});
app.get('/users-latest', function(req, res) {
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) {
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) {
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) {
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("users-search", "users") + templates['footer']);
@@ -63,23 +64,22 @@ var user = require('./../user.js'),
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
next();
return;
return next();
}
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug, 'account') + templates['footer']);
res.send(header + app.create_route('users/' + req.params.userslug, 'account') + templates['footer']);
});
});
});
});
app.get('/users/:userslug/edit', function(req, res) {
if(!req.user)
return res.redirect('/403');
user.getUserField(req.user.uid, 'userslug', function(userslug) {
user.getUserField(req.user.uid, 'userslug', function(err, userslug) {
if(req.params.userslug && userslug === req.params.userslug) {
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/edit','accountedit') + templates['footer']);
@@ -87,46 +87,46 @@ var user = require('./../user.js'),
} 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) {
user.getUserField(req.user.uid, 'userslug', function(err, 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');
}
});
});
});
app.post('/users/uploadpicture', function(req, res) {
if(!req.user)
return res.redirect('/403');
if(req.files.userPhoto.size > 262144) {
res.send({
error: 'Images must be smaller than 256kb!'
});
return;
}
var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
if(allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
res.send({
error: 'Allowed image types are png, jpg and gif!'
});
return;
return;
}
user.getUserField(req.user.uid, 'uploadedpicture', function(oldpicture) {
user.getUserField(req.user.uid, 'uploadedpicture', function(err, oldpicture) {
if(!oldpicture) {
uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res);
return;
@@ -135,15 +135,15 @@ var user = require('./../user.js'),
var absolutePath = path.join(global.configuration['ROOT_DIRECTORY'], global.nconf.get('upload_path'), path.basename(oldpicture));
fs.unlink(absolutePath, function(err) {
if(err) {
console.error('[%d] %s', Date.now(), + err);
if(err) {
winston.error('[%d] %s', Date.now(), + err);
}
uploadUserPicture(req.user.uid, path.extname(req.files.userPhoto.name), req.files.userPhoto.path, res);
});
});
});
function uploadUserPicture(uid, extension, tempPath, res) {
if(!extension) {
res.send({
@@ -154,10 +154,9 @@ var user = require('./../user.js'),
var filename = uid + '-profileimg' + extension;
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);
winston.info('Attempting upload to: '+ uploadPath);
var is = fs.createReadStream(tempPath);
var os = fs.createWriteStream(uploadPath);
@@ -176,11 +175,7 @@ var user = require('./../user.js'),
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
// to avoid poisoning the main process on error or allowing a significant problem
// to crash the main process
console.error('[%d] %s', Date.now(), + err);
winston.err(err.message, err.stack);
}
res.json({ path: imageUrl });
@@ -189,7 +184,7 @@ var user = require('./../user.js'),
});
os.on('error', function(err) {
console.error('[%d] %s', Date.now(), + err);
winston.error('[%d] %s', Date.now(), + err);
});
is.pipe(os);
@@ -199,24 +194,24 @@ var user = require('./../user.js'),
if(!req.user)
return res.redirect('/403');
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
res.redirect('/404');
return;
}
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/following','following') + templates['footer']);
});
});
});
app.get('/users/:userslug/followers', function(req, res) {
if(!req.user)
return res.redirect('/403');
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
res.redirect('/404');
@@ -228,71 +223,43 @@ var user = require('./../user.js'),
});
});
function api_method(req, res) {
app.get('/users/:userslug/favourites', function(req, res) {
if(!req.user)
return res.redirect('/403');
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
res.redirect('/404');
return;
}
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route('users/'+req.params.userslug+'/favourites','favourites') + templates['footer']);
});
});
});
app.get('/api/users/:userslug/following', function(req, res) {
var callerUID = req.user ? req.user.uid : 0;
var userslug = req.params.userslug;
var section = req.params.section ? String(req.params.section).toLowerCase() : null;
if (!section && !userslug) {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.joindate - a.joindate;
});
res.json({ search_display: 'none', users: data });
});
}
else if(section === 'following') {
getFollowing(req, res, callerUID);
}
else if(section === 'followers') {
getFollowers(req, res, callerUID);
}
else if (section === 'edit') {
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.filter(function(p) {return p.deleted !== "1";});
userData.isFollowing = isFollowing;
userData.signature = postTools.markdownToHTML(userData.signature, true);
res.json(userData);
});
});
} else {
res.json(404, { error: 'User not found!' }) ;
}
});
}
}
function getFollowing(req, res, callerUid) {
getUserDataByUserSlug(req.params.userslug, callerUid, function(userData) {
getUserDataByUserSlug(req.params.userslug, callerUID, function(userData) {
if(userData) {
user.getFollowing(userData.uid, function(followingData) {
userData.following = followingData;
userData.followingCount = followingData.length;
res.json(userData);
});
} else {
res.json(404, { error: 'User not found!' }) ;
}
});
}
});
function getFollowers(req, res, callerUid) {
getUserDataByUserSlug(req.params.userslug, callerUid, function(userData) {
app.get('/api/users/:userslug/followers', function(req, res) {
var callerUID = req.user ? req.user.uid : 0;
getUserDataByUserSlug(req.params.userslug, callerUID, function(userData) {
if(userData) {
user.getFollowers(userData.uid, function(followersData) {
userData.followers = followersData;
@@ -301,23 +268,35 @@ var user = require('./../user.js'),
});
} else {
res.json(404, { error: 'User not found!' }) ;
}
}
});
}
});
app.get('/api/users/:userslug/edit', function(req, res) {
var callerUID = req.user ? req.user.uid : 0;
getUserDataByUserSlug(req.params.userslug, callerUID, function(userData) {
res.json(userData);
});
});
app.get('/api/users/:userslug/settings', function(req, res, next) {
var callerUID = req.user ? req.user.uid : 0;
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") {
if(uid !== callerUID || callerUID === "0") {
res.json(403, { error: 'Not allowed!' });
return;
}
user.getUserFields(uid, ['username','userslug','showemail'], function(userData) {
user.getUserFields(uid, ['username','userslug','showemail'], function(err, userData) {
if(err)
return next(err);
if(userData) {
if(userData.showemail && userData.showemail === "1")
userData.showemail = "checked";
@@ -326,56 +305,105 @@ var user = require('./../user.js'),
res.json(userData);
} else {
res.json(404, { error: 'User not found!' }) ;
}
}
});
});
}
});
app.get('/api/users/:userslug?/:section?', api_method);
app.get('/api/users/:userslug/favourites', function(req, res, next) {
var callerUID = req.user ? req.user.uid : 0;
user.get_uid_by_userslug(req.params.userslug, function(uid) {
if(!uid) {
res.json(404, { error: 'User not found!' }) ;
return;
}
if(uid !== callerUID || callerUID === "0") {
res.json(403, { error: 'Not allowed!' });
return;
}
user.getUserFields(uid, ['username','userslug'], function(err, userData) {
if(err)
return next(err);
if(userData) {
posts.getFavourites(uid, function(err, posts) {
if(err)
return next(err);
userData.posts = posts;
userData.show_nofavourites = posts.length?'hide':'show';
res.json(userData);
});
} else {
res.json(404, { error: 'User not found!' }) ;
}
});
});
});
app.get('/api/users/:userslug', function(req, res) {
var callerUID = req.user ? req.user.uid : 0;
getUserDataByUserSlug(req.params.userslug, callerUID, function(userData) {
if(userData) {
user.isFollowing(callerUID, userData.theirid, function(isFollowing) {
posts.getPostsByUid(userData.theirid, 0, 9, function(posts) {
userData.posts = posts.filter(function(p) {return p.deleted !== "1";});
userData.isFollowing = isFollowing;
userData.signature = postTools.markdownToHTML(userData.signature, true);
if(!userData.profileviews)
userData.profileviews = 1;
if(callerUID !== userData.uid)
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
res.json(userData);
});
});
} else {
res.json(404, { error: 'User not found!' }) ;
}
});
});
app.get('/api/users', getUsersSortedByJoinDate);
app.get('/api/users-sort-posts', getUsersSortedByPosts);
app.get('/api/users-sort-reputation', getUsersSortedByReputation);
app.get('/api/users-latest', getUsersSortedByJoinDate);
app.get('/api/users-search', getUsersForSearch);
function getUsersSortedByJoinDate(req, res) {
user.getUsers('users:joindate', 0, 49, function(err, data) {
res.json({ search_display: 'none', loadmore_display:'block', users:data });
});
}
function getUsersSortedByPosts(req, res) {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.postcount - a.postcount;
});
res.json({ search_display: 'none', users:data });
user.getUsers('users:postcount', 0, 49, function(err, data) {
res.json({ search_display: 'none', loadmore_display:'block', users:data });
});
}
function getUsersSortedByReputation(req, res) {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.reputation - a.reputation;
});
res.json({ search_display: 'none', users:data });
user.getUsers('users:reputation', 0, 49, function(err, data) {
res.json({ search_display: 'none', loadmore_display:'block', users:data });
});
}
function getUsersSortedByJoinDate(req, res) {
user.getUserList(function(data) {
data = data.sort(function(a, b) {
return b.joindate - a.joindate;
});
res.json({ search_display: 'none', users:data });
});
}
function getUsersForSearch(req, res) {
res.json({ search_display: 'block', users: [] });
function getUsersForSearch(req, res) {
res.json({ search_display: 'block', loadmore_display:'none', users: [] });
}
function getUserDataByUserSlug(userslug, callerUID, callback) {
user.get_uid_by_userslug(userslug, function(uid) {
if(uid === null) {
callback(null);
return;
}
user.getUserData(uid, function(data) {
if(data) {
data.joindate = utils.relativeTime(data.joindate);
@@ -385,19 +413,20 @@ var user = require('./../user.js'),
} else {
data.age = new Date().getFullYear() - new Date(data.birthday).getFullYear();
}
function canSeeEmail() {
return callerUID === uid || (data.email && (data.showemail && data.showemail === "1"));
}
if(!canSeeEmail())
if(!canSeeEmail())
data.email = "";
if(callerUID === uid && data.showemail === "0")
if(callerUID === uid && (!data.showemail || data.showemail === "0"))
data.emailClass = "";
else
else
data.emailClass = "hide";
data.show_banned = data.banned === '1'?'':'hide';
data.uid = uid;
data.yourid = callerUID;
@@ -414,7 +443,7 @@ var user = require('./../user.js'),
callback(null);
}
});
});
}

View File

@@ -14,7 +14,6 @@
return {
/* sets */
tid: 'topics:tid',
read_by_uid: 'tid:' + tid + ':read_by_uid',
/* sorted sets */

View File

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

View File

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

View File

@@ -10,7 +10,9 @@ var RDB = require('./redis.js')
postTools = require('./postTools'),
async = require('async'),
feed = require('./feed.js'),
favourites = require('./favourites.js');
favourites = require('./favourites.js'),
reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch');
marked.setOptions({
breaks: true
@@ -18,8 +20,6 @@ marked.setOptions({
(function(Topics) {
Topics.minimumTitleLength = 3;
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
if(err === null)
@@ -31,8 +31,7 @@ marked.setOptions({
Topics.getTopicDataWithUsername = function(tid, callback) {
Topics.getTopicData(tid, function(topic) {
user.getUserField(topic.uid, 'username', function(username) {
user.getUserField(topic.uid, 'username', function(err, username) {
topic.username = username;
callback(topic);
});
@@ -41,15 +40,17 @@ marked.setOptions({
Topics.getTopicPosts = function(tid, start, end, current_user, callback) {
posts.getPostsByTid(tid, start, end, function(postData) {
if(Array.isArray(postData) && !postData.length)
return callback([]);
function getFavouritesData(next) {
var pids = [];
for(var i=0; i<postData.length; ++i)
for(var i=0; i<postData.length; ++i)
pids.push(postData[i].pid);
favourites.getFavouritesByPostIDs(pids, current_user, function(fav_data) {
next(null, fav_data);
});
next(null, fav_data);
});
}
function addUserInfoToPosts(next) {
@@ -59,24 +60,26 @@ marked.setOptions({
});
}
async.each(postData, iterator, function(err) {
async.each(postData, iterator, function(err) {
next(err, null);
});
}
}
function getPrivileges(next) {
threadTools.privileges(tid, current_user, function(privData) {
next(null, privData);
});
}
}
async.parallel([getFavouritesData, addUserInfoToPosts, getPrivileges], function(err, results) {
var fav_data = results[0],
privileges = results[2];
for(var i=0; i<postData.length; ++i) {
postData[i].fav_button_class = fav_data[postData[i].pid]? 'btn-warning' : '';
postData[i].fav_star_class = fav_data[postData[i].pid] ? 'icon-star' : 'icon-star-empty';
postData[i]['display_moderator_tools'] = (postData[i].uid == current_user || privileges.editable) ? 'show' : 'none';
postData[i].show_banned = postData[i].user_banned === '1'?'show':'hide';
}
callback(postData);
@@ -85,14 +88,249 @@ marked.setOptions({
}
Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(cid) {
Topics.getTopicField(tid, 'cid', function(err, cid) {
categories.getCategoryData(cid, callback);
});
}
Topics.getLatestTopics = function(current_user, start, end, callback) {
var timestamp = Date.now();
var args = [ 'topics:recent', '+inf', timestamp - 86400000, 'WITHSCORES', 'LIMIT', start, end - start + 1];
RDB.zrevrangebyscore(args, function(err, tids) {
var latestTopics = {
'category_name' : 'Recent',
'show_sidebar' : 'hidden',
'show_topic_button' : 'hidden',
'no_topics_message' : 'hidden',
'topic_row_size': 'span12',
'category_id': false,
'topics' : []
};
if (!tids || !tids.length) {
latestTopics.no_topics_message = 'show';
callback(latestTopics);
return;
}
Topics.getTopicsByTids(tids, current_user, function(topicData) {
latestTopics.topics = topicData;
callback(latestTopics);
});
});
}
Topics.getTotalUnread = function(uid, callback) {
var unreadTids = [],
start = 0,
stop = 21,
done = false;
async.whilst(
function () { return unreadTids.length < 21 && !done; },
function (callback) {
RDB.zrevrange('topics:recent', start, stop, function(err, tids) {
if(err)
return callback(err);
if(tids && !tids.length) {
done = true;
return callback(null);
}
Topics.hasReadTopics(tids, uid, function(read) {
var newtids = tids.filter(function(tid, index, self) {
return read[index] === 0;
});
unreadTids.push.apply(unreadTids, newtids);
start = stop + 1;
stop = start + 21;
callback(null);
});
});
},
function (err) {
callback({
count: unreadTids.length
});
}
);
};
Topics.getUnreadTopics = function(uid, start, stop, callback) {
var unreadTopics = {
'category_name' : 'Unread',
'show_sidebar' : 'hidden',
'show_topic_button' : 'hidden',
'show_markallread_button': 'show',
'no_topics_message' : 'hidden',
'topic_row_size': 'span12',
'topics' : []
};
function noUnreadTopics() {
unreadTopics.no_topics_message = 'show';
unreadTopics.show_markallread_button = 'hidden';
callback(unreadTopics);
}
function sendUnreadTopics(topicIds) {
Topics.getTopicsByTids(topicIds, uid, function(topicData) {
unreadTopics.topics = topicData;
unreadTopics.nextStart = start + topicIds.length;
if(!topicData || topicData.length === 0)
unreadTopics.no_topics_message = 'show';
if(uid === 0 || topicData.length === 0)
unreadTopics.show_markallread_button = 'hidden';
callback(unreadTopics);
});
}
var unreadTids = [],
done = false;
async.whilst(
function () { return unreadTids.length < 20 && !done; },
function (callback) {
RDB.zrevrange('topics:recent', start, stop, function(err, tids) {
if(err)
return callback(err);
if(tids && !tids.length) {
done = true;
return callback(null);
}
if(uid === 0) {
unreadTids.push.apply(unreadTids, tids);
callback(null);
} else {
Topics.hasReadTopics(tids, uid, function(read) {
var newtids = tids.filter(function(tid, index, self) {
return read[index] === 0;
});
unreadTids.push.apply(unreadTids, newtids);
start = stop + 1;
stop = start + 19;
callback(null);
});
}
});
},
function (err) {
if(err)
return callback([]);
if(unreadTids.length)
sendUnreadTopics(unreadTids);
else
noUnreadTopics();
}
);
}
Topics.getTopicsByTids = function(tids, current_user, callback, category_id) {
var retrieved_topics = [];
if(!Array.isArray(tids) || tids.length === 0) {
callback(retrieved_topics);
return;
}
function getTopicInfo(topicData, callback) {
function getUserInfo(next) {
user.getUserFields(topicData.uid, ['username'], next);
}
function hasReadTopic(next) {
Topics.hasReadTopic(topicData.tid, current_user, function(hasRead) {
next(null, hasRead);
});
}
function getTeaserInfo(next) {
Topics.getTeaser(topicData.tid, function(err, teaser) {
next(null, teaser || {});
});
}
// temporary. I don't think this call should belong here
function getPrivileges(next) {
categories.privileges(category_id, current_user, function(user_privs) {
next(null, user_privs);
});
}
async.parallel([getUserInfo, hasReadTopic, getTeaserInfo, getPrivileges], function(err, results) {
callback({
username: results[0].username,
userbanned: results[0].banned,
hasread: results[1],
teaserInfo: results[2],
privileges: results[3]
});
});
}
function isTopicVisible(topicData, topicInfo) {
var deleted = parseInt(topicData.deleted, 10) !== 0;
return !deleted || (deleted && topicInfo.privileges.view_deleted) || topicData.uid === current_user;
}
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';
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : '';
topicData.relativeTime = utils.relativeTime(topicData.timestamp);
topicData.username = topicInfo.username;
topicData.badgeclass = (topicInfo.hasread && current_user != 0) ? '' : 'badge-important';
topicData.teaser_text = topicInfo.teaserInfo.text || '',
topicData.teaser_username = topicInfo.teaserInfo.username || '';
topicData.teaser_userpicture = topicInfo.teaserInfo.picture || '';
topicData.teaser_timestamp = topicInfo.teaserInfo.timestamp ? utils.relativeTime(topicInfo.teaserInfo.timestamp) : '';
if (isTopicVisible(topicData, topicInfo))
retrieved_topics.push(topicData);
callback(null);
});
});
}
async.eachSeries(tids, loadTopic, function(err) {
if(!err) {
callback(retrieved_topics);
}
});
}
Topics.getTopicWithPosts = function(tid, current_user, callback) {
threadTools.exists(tid, function(exists) {
if (!exists)
if (!exists)
return callback(new Error('Topic tid \'' + tid + '\' not found'));
Topics.markAsRead(tid, current_user);
@@ -104,8 +342,7 @@ marked.setOptions({
}
function getTopicPosts(next) {
Topics.getTopicPosts(tid, 0, -1, current_user, function(topicPosts, privileges) {
Topics.getTopicPosts(tid, 0, 10, current_user, function(topicPosts, privileges) {
next(null, topicPosts);
});
}
@@ -114,8 +351,8 @@ marked.setOptions({
threadTools.privileges(tid, current_user, function(privData) {
next(null, privData);
});
}
}
function getCategoryData(next) {
Topics.getCategoryData(tid, next);
}
@@ -126,14 +363,14 @@ marked.setOptions({
callback(err, null);
return;
}
var topicData = results[0],
topicPosts = results[1],
privileges = results[2],
categoryData = results[3];
var main_posts = topicPosts.splice(0, 1);
callback(null, {
'topic_name':topicData.title,
'category_name':categoryData.name,
@@ -162,7 +399,7 @@ marked.setOptions({
function getReadStatus(next) {
if (uid && parseInt(uid) > 0) {
RDB.sismember(schema.topics(tid).read_by_uid, uid, function(err, read) {
Topics.hasReadTopic(tid, uid, function(read) {
next(null, read);
});
} else {
@@ -181,7 +418,7 @@ marked.setOptions({
if (err) {
throw new Error(err);
}
var topicData = results[0],
hasRead = results[1],
teaser = results[2];
@@ -192,7 +429,7 @@ marked.setOptions({
topicData.teaser_username = teaser.username || '';
topicData.teaser_timestamp = teaser.timestamp ? utils.relativeTime(teaser.timestamp) : '';
topicData.teaser_userpicture = teaser.picture;
callback(topicData);
});
}
@@ -235,24 +472,42 @@ marked.setOptions({
});
}
Topics.markAllRead = function(uid, callback) {
RDB.smembers('topics:tid', function(err, tids) {
if(err) {
console.log(err);
callback(err, null);
return;
}
if(tids && tids.length) {
for(var i=0; i<tids.length; ++i) {
Topics.markAsRead(tids[i], uid);
}
}
callback(null, true);
});
}
Topics.getTitleByPid = function(pid, callback) {
posts.getPostField(pid, 'tid', function(tid) {
Topics.getTopicField(tid, 'title', function(title) {
Topics.getTopicField(tid, 'title', function(err, title) {
callback(title);
});
});
}
Topics.markUnRead = function(tid) {
RDB.del('tid:' + tid + ':read_by_uid');
RDB.del('tid:' + tid + ':read_by_uid');
}
Topics.markAsRead = function(tid, uid) {
RDB.sadd(schema.topics(tid).read_by_uid, uid);
Topics.getTopicField(tid, 'cid', function(cid) {
Topics.getTopicField(tid, 'cid', function(err, cid) {
categories.isTopicsRead(cid, uid, function(read) {
if(read) {
categories.markAsRead(cid, uid);
@@ -265,9 +520,9 @@ marked.setOptions({
var batch = RDB.multi();
for (var i=0, ii=tids.length; i<ii; i++) {
batch.sismember(schema.topics(tids[i]).read_by_uid, uid);
batch.sismember(schema.topics(tids[i]).read_by_uid, uid);
}
batch.exec(function(err, hasRead) {
callback(hasRead);
});
@@ -275,16 +530,16 @@ marked.setOptions({
Topics.hasReadTopic = function(tid, uid, callback) {
RDB.sismember(schema.topics(tid).read_by_uid, uid, function(err, hasRead) {
if(err === null) {
callback(hasRead);
}
else {
} else {
console.log(err);
callback(false);
}
});
});
}
Topics.getTeasers = function(tids, callback) {
var teasers = [];
if (Array.isArray(tids)) {
@@ -305,13 +560,18 @@ marked.setOptions({
if (!err) {
posts.getPostFields(pid, ['content', 'uid', 'timestamp'], function(postData) {
user.getUserFields(postData.uid, ['username', 'picture'], function(userData) {
user.getUserFields(postData.uid, ['username', 'picture'], function(err, userData) {
if(err)
return callback(err, null);
var stripped = postData.content,
timestamp = postData.timestamp;
if(postData.content)
stripped = utils.strip_tags(postTools.markdownToHTML(postData.content));
if(postData.content) {
stripped = postData.content.replace(/>.+\n\n/, '');
stripped = utils.strip_tags(postTools.markdownToHTML(stripped));
}
callback(null, {
"text": stripped,
"username": userData.username,
@@ -329,48 +589,35 @@ marked.setOptions({
type: 'error',
timeout: 2000,
title: 'Title too short',
message: "Please enter a longer title. At least " + Topics.minimumTitleLength + " characters.",
message: "Please enter a longer title. At least " + meta.config.minimumTitleLength + " characters.",
alert_id: 'post_error'
});
}
Topics.post = function(socket, uid, title, content, category_id, images) {
if (!category_id)
Topics.post = function(uid, title, content, category_id, images, callback) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if(content)
if(content)
content = content.trim();
if(title)
title = title.trim();
if (uid === 0) {
socket.emit('event:alert', {
title: 'Thank you for posting',
message: 'Since you are unregistered, your post is awaiting approval. Click here to register now.',
type: 'warning',
timeout: 7500,
clickfn: function() {
ajaxify.go('register');
}
});
return; // for now, until anon code is written.
} else if(!title || title.length < Topics.minimumTitleLength) {
Topics.emitTitleTooShortAlert(socket);
callback(new Error('not-logged-in'), null);
return;
} else if (!content || content.length < posts.miminumPostLength) {
posts.emitContentTooShortAlert(socket);
} else if(!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
return;
} else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
user.getUserField(uid, 'lastposttime', function(lastposttime) {
if(Date.now() - lastposttime < config.post_delay) {
socket.emit('event:alert', {
title: 'Too many posts!',
message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.',
type: 'error',
timeout: 2000
});
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if(Date.now() - lastposttime < meta.config.postDelay) {
callback(new Error('too-many-posts'), null);
return;
}
@@ -380,7 +627,7 @@ marked.setOptions({
// Global Topics
if (uid == null) uid = 0;
if (uid !== null) {
RDB.sadd(schema.topics().tid, tid);
RDB.sadd('topics:tid', tid);
} else {
// need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush(schema.topics().queued_tids, tid);
@@ -400,28 +647,12 @@ marked.setOptions({
'postcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
'pinned': 0
});
topicSearch.index(title, tid);
RDB.set('topicslug:' + slug + ':tid', tid);
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);
// Notify any users looking at the category that a new topic has arrived
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
io.sockets.in('recent_posts').emit('event:new_topic', topicData);
});
posts.getTopicPostStats(socket);
}
});
user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category
@@ -431,29 +662,37 @@ 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);
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
feed.updateCategory(category_id);
socket.emit('event:alert', {
title: 'Thank you for posting',
message: 'You have successfully posted. Click here to view your post.',
type: 'notify',
timeout: 2000
posts.create(uid, tid, content, images, function(postData) {
if (postData) {
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
// Notify any users looking at the category that a new topic has arrived
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
io.sockets.in('recent_posts').emit('event:new_topic', topicData);
});
callback(null, postData);
}
});
});
});
};
Topics.getTopicField = function(tid, field, callback) {
RDB.hget('topic:' + tid, field, function(err, data) {
if(err === null)
callback(data);
else
console.log(err);
});
RDB.hget('topic:' + tid, field, callback);
}
Topics.getTopicFields = function(tid, fields, callback) {
RDB.hmgetObject('topic:' + tid, fields, callback);
}
Topics.setTopicField = function(tid, field, value) {
@@ -465,16 +704,16 @@ marked.setOptions({
}
Topics.isLocked = function(tid, callback) {
Topics.getTopicField(tid, 'locked', function(locked) {
Topics.getTopicField(tid, 'locked', function(err, locked) {
callback(locked);
});
}
Topics.updateTimestamp = function(tid, timestamp) {
RDB.zadd(schema.topics().recent, timestamp, tid);
RDB.zadd('topics:recent', timestamp, tid);
Topics.setTopicField(tid, 'lastposttime', timestamp);
}
Topics.addPostToTopic = function(tid, pid) {
RDB.rpush('tid:' + tid + ':posts', pid);
}
@@ -483,4 +722,49 @@ marked.setOptions({
RDB.lrange('tid:' + tid + ':posts', 0, -1, callback);
}
Topics.delete = function(tid) {
Topics.setTopicField(tid, 'deleted', 1);
RDB.zrem('topics:recent', tid);
}
Topics.restore = function(tid) {
Topics.setTopicField(tid, 'deleted', 0);
Topics.getTopicField(tid, 'lastposttime', function(err, lastposttime) {
RDB.zadd('topics:recent', lastposttime, tid);
});
}
Topics.reIndexTopic = function(tid, callback) {
Topics.getPids(tid, function(err, pids) {
if(err) {
callback(err);
} else {
posts.reIndexPids(pids, function(err) {
if(err) {
callback(err);
} else {
callback(null);
}
});
}
});
}
Topics.reIndexAll = function(callback) {
RDB.smembers('topics:tid', function(err, tids) {
if(err) {
callback(err, null);
} else {
async.each(tids, Topics.reIndexTopic, function(err) {
if(err) {
callback(err, null);
} else {
callback(null, 'All topics reindexed.');
}
});
}
});
}
}(exports));

100
src/upgrade.js Normal file
View File

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

View File

@@ -2,7 +2,8 @@ var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
crypto = require('crypto'),
emailjs = require('emailjs'),
emailjsServer = emailjs.server.connect(config.mailer),
meta = require('./meta.js'),
emailjsServer = emailjs.server.connect(meta.config.mailer),
bcrypt = require('bcrypt'),
marked = require('marked'),
notifications = require('./notifications.js'),
@@ -41,12 +42,13 @@ var utils = require('./../public/src/utils.js'),
} else next();
}
], function(err, results) {
if (err) return callback(err, 0); // FIXME: Maintaining the 0 for backwards compatibility. Do we need this?
if (err) return callback(err, null);
RDB.incr('global:next_user_id', function(err, uid) {
RDB.handle(err);
var gravatar = User.createGravatarURLFromEmail(email);
var timestamp = Date.now();
RDB.hmset('user:'+uid, {
'uid': uid,
@@ -58,17 +60,19 @@ var utils = require('./../public/src/utils.js'),
'website':'',
'email' : email || '',
'signature':'',
'joindate' : Date.now(),
'joindate' : timestamp,
'picture': gravatar,
'gravatarpicture' : gravatar,
'uploadedpicture': '',
'profileviews': 0,
'reputation': 0,
'postcount': 0,
'lastposttime': 0,
'administrator': (uid == 1) ? 1 : 0,
'banned': 0,
'showemail': 0
});
RDB.set('username:' + username + ':uid', uid);
RDB.set('userslug:'+ userslug +':uid', uid);
@@ -83,8 +87,10 @@ var utils = require('./../public/src/utils.js'),
io.sockets.emit('user.count', {count: count});
});
RDB.lpush('userlist', uid);
RDB.zadd('users:joindate', timestamp, uid);
RDB.zadd('users:postcount', 0, uid);
RDB.zadd('users:reputation', 0, uid);
io.sockets.emit('user.latest', {userslug: userslug, username: username});
if (password !== undefined) {
@@ -97,7 +103,7 @@ var utils = require('./../public/src/utils.js'),
});
});
};
User.delete = function(uid, callback) {
RDB.exists('user:'+uid, function(err, exists) {
if(exists === 1) {
@@ -112,7 +118,9 @@ var utils = require('./../public/src/utils.js'),
RDB.del('followers:' + uid);
RDB.del('following:' + uid);
RDB.lrem('userlist', 1, uid);
RDB.zrem('users:joindate', uid);
RDB.zrem('users:postcount', uid);
RDB.zrem('users:reputation', uid);
callback(true);
});
@@ -121,25 +129,21 @@ var utils = require('./../public/src/utils.js'),
}
});
}
User.ban = function(uid, callback) {
User.setUserField(uid, 'banned', 1, callback);
}
User.unban = function(uid, callback) {
User.setUserField(uid, 'banned', 0, callback);
}
User.getUserField = function(uid, field, callback) {
RDB.hget('user:' + uid, field, function(err, data) {
if(err === null) {
callback(data);
} else {
console.log(err);
}
});
RDB.hget('user:' + uid, field, callback);
}
User.getUserFields = function(uid, fields, callback) {
RDB.hmgetObject('user:' + uid, fields, function(err, data) {
if(err === null) {
callback(data);
} else {
console.log(err);
}
});
RDB.hmgetObject('user:' + uid, fields, callback);
}
User.getMultipleUserFields = function(uids, fields, callback) {
@@ -155,7 +159,9 @@ var utils = require('./../public/src/utils.js'),
});
function iterator(uid, callback) {
User.getUserFields(uid, fields, function(userData) {
User.getUserFields(uid, fields, function(err, userData) {
if(err)
return callback(err);
returnData.push(userData);
callback(null);
});
@@ -185,74 +191,92 @@ var utils = require('./../public/src/utils.js'),
});
}
User.updateProfile = function(socket, uid, data) {
User.filterBannedUsers = function(users) {
return users.filter(function(user) {
return (!user.banned || user.banned === '0');
});
}
User.updateProfile = function(uid, data, callback) {
var fields = ['email', 'fullname', 'website', 'location', 'birthday', 'signature'];
var returnData = {success:false};
function isSignatureValid(next) {
if(data['signature'] !== undefined && data['signature'].length > 150) {
next({error:'Signature can\'t be longer than 150 characters!'}, false);
} else {
next(null, true);
}
}
function isEmailAvailable(next) {
if(data['email'] !== undefined) {
User.getUserField(uid, 'email', function(email) {
if(email !== data['email']) {
User.isEmailAvailable(data['email'], function(available) {
if(!available) {
next({error:'Email not available!'}, false);
}
});
} else {
next(null, true);
}
});
} else {
next(null, true);
}
}
function isEmailAvailable(next) {
if(!data['email']) {
return next(null, true);
}
User.getUserField(uid, 'email', function(err, email) {
if(email !== data['email']) {
User.isEmailAvailable(data['email'], function(available) {
if(!available) {
next({error:'Email not available!'}, false);
} else {
next(null, true);
}
});
} else {
next(null, true);
}
});
}
async.series([isSignatureValid, isEmailAvailable], function(err, results) {
if(err) {
socket.emit('event:alert', {
title: 'Error',
message: err.error,
type: 'error',
timeout: 2000
});
console.log(err);
callback(returnData);
} else {
updateFields();
async.each(fields, updateField, function(err) {
if(err) {
console.log(err);
callback(returnData);
} else {
returnData.success = true;
callback(returnData);
}
});
}
});
function updateFields() {
for(var i = 0, key, ii = fields.length; i < ii; ++i) {
key = fields[i];
function updateField(field, callback) {
if(data[field] !== undefined) {
if(field === 'email') {
var gravatarpicture = User.createGravatarURLFromEmail(data[field]);
User.setUserField(uid, 'gravatarpicture', gravatarpicture);
User.getUserFields(uid, ['email', 'picture', 'uploadedpicture'], function(err, userData) {
if(err)
return callback(err);
if(data[key] !== undefined) {
if(key === 'email') {
User.setUserField(uid, 'gravatarpicture', User.createGravatarURLFromEmail(data[key]));
user.getUserField(uid, 'email', function(email) {
RDB.del('email:' + email + ':uid');
RDB.set('email:' + data['email'] + ':uid', uid);
});
} else if(key === 'signature') {
data[key] = utils.strip_tags(data[key]);
}
User.setUserField(uid, key, data[key]);
RDB.del('email:' + userData['email'] + ':uid');
RDB.set('email:' + data['email'] + ':uid', uid);
User.setUserField(uid, field, data[field]);
if(userData.picture !== userData.uploadedpicture) {
returnData.picture = gravatarpicture;
User.setUserField(uid, 'picture', gravatarpicture);
}
returnData.gravatarpicture = gravatarpicture;
callback(null);
});
return;
} else if(field === 'signature') {
data[field] = utils.strip_tags(data[field]);
}
}
socket.emit('event:alert', {
title: 'Success',
message: 'Your profile has been updated successfully!',
type: 'success',
timeout: 2000
});
User.setUserField(uid, field, data[field]);
callback(null);
} else {
callback(null);
}
}
}
@@ -268,23 +292,17 @@ var utils = require('./../public/src/utils.js'),
});
}
User.changePassword = function(socket, uid, data, callback) {
User.changePassword = function(uid, data, callback) {
if(!utils.isPasswordValid(data.newPassword)) {
socket.emit('event:alert', {
title: 'Error',
message: 'Invalid password!',
type: 'error',
timeout: 2000
});
callback(false);
return;
callback({err:'Invalid password!'});
return;
}
User.getUserField(uid, 'password', function(user_password) {
User.getUserField(uid, 'password', function(err, user_password) {
bcrypt.compare(data.currentPassword, user_password, function(err, res) {
if(err) {
console.log(err);
callback(false);
callback({err:'bcrpyt compare error!'});
return;
}
@@ -292,50 +310,39 @@ var utils = require('./../public/src/utils.js'),
User.hashPassword(data.newPassword, function(hash) {
User.setUserField(uid, 'password', hash);
socket.emit('event:alert', {
title: 'Success',
message: 'Your password is updated!',
type: 'success',
timeout: 2000
});
callback(true);
callback({err:null});
});
} else {
socket.emit('event:alert', {
title: 'Warning',
message: 'Your current password is not correct!',
type: 'warning',
timeout: 2000
});
callback(false);
callback({err:'Your current password is not correct!'});
}
});
});
}
User.setUserField = function(uid, field, value) {
RDB.hset('user:' + uid, field, value);
User.setUserField = function(uid, field, value, callback) {
RDB.hset('user:' + uid, field, value, callback);
}
User.setUserFields = function(uid, data) {
RDB.hmset('user:' + uid, data);
}
User.incrementUserFieldBy = function(uid, field, value) {
RDB.hincrby('user:' + uid, field, value);
User.incrementUserFieldBy = function(uid, field, value, callback) {
RDB.hincrby('user:' + uid, field, value, callback);
}
User.decrementUserFieldBy = function(uid, field, value) {
RDB.hincrby('user:' + uid, field, -value);
User.decrementUserFieldBy = function(uid, field, value, callback) {
RDB.hincrby('user:' + uid, field, -value, callback);
}
User.getUserList = function(callback) {
User.getUsers = function(set, start, stop, callback) {
var data = [];
RDB.lrange('userlist', 0, -1, function(err, uids) {
RDB.zrevrange(set, start, stop, function(err, uids) {
if(err) {
return callback(err, null);
}
RDB.handle(err);
function iterator(uid, callback) {
User.getUserData(uid, function(userData) {
if(userData) {
@@ -344,14 +351,9 @@ var utils = require('./../public/src/utils.js'),
callback(null);
});
}
async.each(uids, iterator, function(err) {
if(!err) {
callback(data);
} else {
console.log(err);
callback(null);
}
async.eachSeries(uids, iterator, function(err) {
callback(err, data);
});
});
}
@@ -362,13 +364,13 @@ var utils = require('./../public/src/utils.js'),
default: 'identicon',
rating: 'pg'
};
if (!email) {
email = '';
options.forcedefault = 'y';
}
return require('gravatar').url(email, options, https=global.nconf.get('https'));
return require('gravatar').url(email, options, https=nconf.get('https'));
}
User.hashPassword = function(password, callback) {
@@ -377,7 +379,7 @@ var utils = require('./../public/src/utils.js'),
return;
}
bcrypt.genSalt(config.bcrypt_rounds, function(err, salt) {
bcrypt.genSalt(nconf.get('bcrypt_rounds'), function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
callback(hash);
});
@@ -410,7 +412,10 @@ var utils = require('./../public/src/utils.js'),
User.onNewPostMade = function(uid, tid, pid, timestamp) {
User.addPostIdToUser(uid, pid);
User.incrementUserFieldBy(uid, 'postcount', 1);
User.incrementUserFieldBy(uid, 'postcount', 1, function(err, newpostcount) {
RDB.zadd('users:postcount', newpostcount, uid);
});
User.setUserField(uid, 'lastposttime', timestamp);
User.sendPostNotificationToFollowers(uid, tid, pid);
@@ -439,9 +444,9 @@ var utils = require('./../public/src/utils.js'),
}
User.sendConfirmationEmail = function (email) {
if (global.config['email:host'] && global.config['email:port'] && global.config['email:from']) {
if (meta.config['email:host'] && meta.config['email:port'] && meta.config['email:from']) {
var confirm_code = utils.generateUUID(),
confirm_link = config.url + 'confirm/' + confirm_code,
confirm_link = nconf.get('url') + 'confirm/' + confirm_code,
confirm_email = global.templates['emails/header'] + global.templates['emails/email_confirm'].parse({'CONFIRM_LINK': confirm_link}) + global.templates['emails/footer'],
confirm_email_plaintext = global.templates['emails/email_confirm_plaintext'].parse({ 'CONFIRM_LINK': confirm_link });
@@ -455,10 +460,10 @@ var utils = require('./../public/src/utils.js'),
RDB.set(confirm_key, email);
RDB.expire(confirm_key, expiry_time);
// Send intro email w/ confirm code
// Send intro email w/ confirm code
var message = emailjs.message.create({
text: confirm_email_plaintext,
from: config.mailer.from,
from: meta.config.mailer.from,
to: email,
subject: '[NodeBB] Registration Email Verification',
attachment: [
@@ -566,19 +571,19 @@ var utils = require('./../public/src/utils.js'),
callback(null);
});
}
async.eachSeries(uids, iterator, function(err) {
callback(returnData);
});
}
User.sendPostNotificationToFollowers = function(uid, tid, pid) {
User.getUserField(uid, 'username', function(username) {
User.getUserField(uid, 'username', function(err, username) {
RDB.smembers('followers:' + uid, function(err, followers) {
topics.getTopicField(tid, 'slug', function(slug) {
topics.getTopicField(tid, 'slug', function(err, slug) {
var message = username + ' made a new post';
notifications.create(message, 5, global.nconf.get('url') + 'topic/' + slug + '#' + pid, 'notification_'+ Date.now(), function(nid) {
notifications.create(message, 5, nconf.get('url') + 'topic/' + slug + '#' + pid, 'notification_'+ Date.now(), function(nid) {
notifications.push(nid, followers);
});
});
@@ -611,11 +616,12 @@ var utils = require('./../public/src/utils.js'),
};
User.latest = function(socket) {
RDB.lrange('userlist', 0, 0, function(err, uid) {
RDB.zrevrange('users:joindate', 0, 0, function(err, uid) {
RDB.handle(err);
User.getUserFields(uid, ['username', 'userslug'], function(userData) {
socket.emit('user.latest', {userslug: userData.userslug, username: userData.username});
User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
if(!err && userData)
socket.emit('user.latest', {userslug: userData.userslug, username: userData.username});
});
});
}
@@ -646,14 +652,14 @@ var utils = require('./../public/src/utils.js'),
}
function iterator(uid, callback) {
User.getUserField(uid, 'username', function(username) {
User.getUserField(uid, 'username', function(err, username) {
usernames.push(username);
callback(null);
});
}
async.eachSeries(uids, iterator, function(err) {
callback(usernames);
callback(usernames);
});
}
@@ -665,14 +671,14 @@ var utils = require('./../public/src/utils.js'),
}
function iterator(uid, callback) {
User.getUserField(uid, 'userslug', function(userslug) {
User.getUserField(uid, 'userslug', function(err, userslug) {
userslugs.push(userslug);
callback(null);
});
}
async.eachSeries(uids, iterator, function(err) {
callback(userslugs);
callback(userslugs);
});
}
@@ -781,6 +787,7 @@ var utils = require('./../public/src/utils.js'),
User.reset = {
validate: function(socket, code, callback) {
if (typeof callback !== 'function') {
callback = null;
}
@@ -830,13 +837,13 @@ var utils = require('./../public/src/utils.js'),
RDB.set('reset:' + reset_code + ':uid', uid);
RDB.set('reset:' + reset_code + ':expiry', (60*60)+new Date()/1000|0); // Active for one hour
var reset_link = config.url + 'reset/' + reset_code,
var reset_link = nconf.get('url') + 'reset/' + reset_code,
reset_email = global.templates['emails/reset'].parse({'RESET_LINK': reset_link}),
reset_email_plaintext = global.templates['emails/reset_plaintext'].parse({ 'RESET_LINK': reset_link });
var message = emailjs.message.create({
text: reset_email_plaintext,
from: config.mailer.from,
from: meta.config.mailer?meta.config.mailer.from:'localhost@example.org',
to: email,
subject: 'Password Reset Requested',
attachment: [
@@ -873,14 +880,17 @@ var utils = require('./../public/src/utils.js'),
});
},
commit: function(socket, code, password) {
this.validate(code, function(validated) {
this.validate(socket, code, function(validated) {
if (validated) {
RDB.get('reset:' + code + ':uid', function(err, uid) {
if (err) {
RDB.handle(err);
}
User.setUserField(uid, 'password', password);
User.hashPassword(password, function(hash) {
User.setUserField(uid, 'password', hash);
});
RDB.del('reset:' + code + ':uid');
RDB.del('reset:' + code + ':expiry');
@@ -1066,22 +1076,6 @@ var utils = require('./../public/src/utils.js'),
},
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) {
RDB.handle(err);
}
callback(flag === 1);
});
},
removeFlag: function(uid) {
RDB.del('uid:' + uid + ':notifications:flag', function(err) {
if (err) {
RDB.handle(err);
}
});
}
}
}(exports));

View File

@@ -5,12 +5,12 @@ var express = require('express'),
RedisStore = require('connect-redis')(express),
path = require('path'),
redis = require('redis'),
redisServer = redis.createClient(global.nconf.get('redis:port'), global.nconf.get('redis:host')),
redisServer = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')),
marked = require('marked'),
utils = require('../public/src/utils.js'),
pkg = require('../package.json'),
fs = require('fs'),
user = require('./user.js'),
categories = require('./categories.js'),
posts = require('./posts.js'),
@@ -18,6 +18,7 @@ var express = require('express'),
notifications = require('./notifications.js'),
admin = require('./routes/admin.js'),
userRoute = require('./routes/user.js'),
apiRoute = require('./routes/api.js'),
installRoute = require('./routes/install.js'),
testBed = require('./routes/testbed.js'),
auth = require('./routes/authentication.js'),
@@ -25,7 +26,7 @@ var express = require('express'),
(function(app) {
var templates = null;
/**
* `options` object requires: req, res
* accepts: metaTags
@@ -35,28 +36,31 @@ var express = require('express'),
{ 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' },
{ property: 'og:site_name', content: meta.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',
cssSrc: meta.config['theme:src'] || nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css',
title: meta.config['title'] || 'NodeBB',
browserTitle: meta.config['title'] || 'NodeBB',
csrf: options.res.locals.csrf_token,
relative_path: global.nconf.get('relative_path'),
relative_path: 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;
// 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));
});
// callback(null, templates['header'].parse(templateValues));
// });
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(global.nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public')));
app.use(nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public')));
app.use(express.bodyParser()); // Puts POST vars in request.body
app.use(express.cookieParser()); // If you want to parse cookies (res.cookies)
app.use(express.compress());
@@ -65,8 +69,11 @@ var express = require('express'),
client: redisServer,
ttl: 60*60*24*14
}),
secret: global.nconf.get('secret'),
key: 'express.sid'
secret: nconf.get('secret'),
key: 'express.sid',
cookie: {
maxAge: 60*60*24*30 // 30 days
}
}));
app.use(express.csrf());
app.use(function(req, res, next) {
@@ -79,11 +86,11 @@ var express = require('express'),
}
auth.initialize(app);
app.use(function(req, res, next) {
global.nconf.set('https', req.secure);
nconf.set('https', req.secure);
// Don't bother with session handling for API requests
if (/^\/api\//.test(req.url)) return next();
@@ -96,7 +103,7 @@ var express = require('express'),
next();
});
app.use(app.router);
app.use(function(req, res, next) {
@@ -104,62 +111,61 @@ var express = require('express'),
// respond with html page
if (req.accepts('html')) {
//res.json('404', { url: req.url });
res.redirect(global.nconf.get('relative_path') + '/404');
res.redirect(nconf.get('relative_path') + '/404');
return;
}
// respond with json
if (req.accepts('json')) {
console.log('sending 404 json');
res.send({ error: 'Not found' });
return;
}
// default to plain-text. send()
res.type('txt').send('Not found');
});
app.use(function(err, req, res, next) {
// we may use properties of the error object
// here and next(err) appropriately, or if
// we possibly recovered from the error, simply next().
console.error(err.stack);
res.status(err.status || 500);
res.json('500', { error: err.message });
});
});
app.create_route = function(url, tpl) { // to remove
return '<script>templates.ready(function(){ajaxify.go("' + url + '", null, "' + tpl + '");});</script>';
};
app.namespace(global.nconf.get('relative_path'), function() {
app.namespace(nconf.get('relative_path'), function() {
auth.create_routes(app);
admin.create_routes(app);
userRoute.create_routes(app);
installRoute.create_routes(app);
testBed.create_routes(app);
apiRoute.create_routes(app);
// Basic Routes (entirely client-side parsed, goal is to move the rest of the crap in this file into this one section)
(function() {
var routes = ['login', 'register', 'account', 'recent', 'popular', 'active', '403', '404'];
var routes = ['login', 'register', 'account', 'recent', 'unread', 'popular', 'active', '403', '404'];
for (var i=0, ii=routes.length; i<ii; i++) {
(function(route) {
app.get('/' + route, function(req, res) {
if ((route === 'login' || route ==='register') && (req.user && req.user.uid > 0)) {
user.getUserField(req.user.uid, 'userslug', function(userslug) {
res.redirect('/users/'+userslug);
user.getUserField(req.user.uid, 'userslug', function(err, userslug) {
res.redirect('/users/'+userslug);
});
return;
}
@@ -171,7 +177,7 @@ var express = require('express'),
}(routes[i]));
}
}());
app.get('/', function(req, res) {
async.parallel({
@@ -180,9 +186,9 @@ var express = require('express'),
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') },
{ name: "title", content: meta.config.title || 'NodeBB' },
{ name: "description", content: meta.config.description || '' },
{ property: 'og:title', content: 'Index | ' + (meta.config.title || 'NodeBB') },
{ property: "og:type", content: 'website' }
]
}, next);
@@ -201,9 +207,10 @@ var express = require('express'),
);
})
});
app.get('/topic/:topic_id/:slug?', function(req, res) {
var tid = req.params.topic_id;
if (tid.match(/^\d+\.rss$/)) {
fs.readFile('feeds/topics/' + tid, function (err, data) {
@@ -211,7 +218,7 @@ var express = require('express'),
res.type('text').send(404, "Unable to locate an rss feed at this location.");
return;
}
res.type('xml').set('Content-Length', data.length).send(data);
});
return;
@@ -220,13 +227,19 @@ var express = require('express'),
async.waterfall([
function(next) {
topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), function(err, topicData) {
if(topicData) {
if(topicData.deleted === '1' && topicData.expose_tools === 0)
return next(new Error('Topic deleted'), null);
}
next(err, topicData);
});
},
function(topicData, next) {
var posts = topicData.posts.push(topicData.main_posts[0]),
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;
@@ -235,12 +248,11 @@ var express = require('express'),
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:title', content: topicData.topic_name + ' | ' + (meta.config.title || 'NodeBB') },
{ property: "og:type", content: 'article' },
{ property: "og:url", content: global.nconf.get('url') + 'topic/' + topicData.slug },
{ property: "og:url", content: nconf.get('url') + 'topic/' + topicData.slug },
{ property: 'og:image', content: topicData.main_posts[0].picture },
{ property: "article:published_time", content: new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toISOString() },
{ property: 'article:modified_time', content: new Date(lastMod).toISOString() },
@@ -268,7 +280,7 @@ var express = require('express'),
app.get('/category/:category_id/:slug?', function(req, res) {
var cid = req.params.category_id;
if (cid.match(/^\d+\.rss$/)) {
fs.readFile('feeds/categories/' + cid, function (err, data) {
if (err) {
@@ -291,7 +303,6 @@ var express = require('express'),
app.build_header({
req: req,
res: res,
title: categoryData.category_name,
metaTags: [
{ name: 'title', content: categoryData.category_name },
{ name: 'description', content: categoryData.category_description },
@@ -318,7 +329,7 @@ var express = require('express'),
});
app.get('/confirm/:code', function(req, res) {
app.build_header({ req: req, res: res }, function(header) {
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + '<script>templates.ready(function(){ajaxify.go("confirm/' + req.params.code + '");});</script>' + templates['footer']);
});
});
@@ -336,15 +347,9 @@ var express = require('express'),
res.send( "User-agent: *\n" +
"Disallow: \n" +
"Disallow: /admin/\n" +
"Sitemap: " + global.nconf.get('url') + "sitemap.xml");
"Sitemap: " + nconf.get('url') + "sitemap.xml");
});
app.get('/api/:method', api_method);
app.get('/api/:method/:id', api_method);
// ok fine MUST ADD RECURSION style. I'll look for a better fix in future but unblocking baris for this:
app.get('/api/:method/:id/:section?', api_method);
app.get('/api/:method/:id*', api_method);
app.get('/cid/:cid', function(req, res) {
categories.getCategoryData(req.params.cid, function(err, data) {
if(data)
@@ -373,164 +378,41 @@ var express = require('express'),
});
app.get('/outgoing', function(req, res) {
var url = req.url.split('?');
if (!req.query.url) return res.redirect('/404');
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');
}
app.build_header({ req: req, res: res }, function(err, header) {
res.send(
header +
'\n\t<script>templates.ready(function(){ajaxify.go("outgoing?url=' + encodeURIComponent(req.query.url) + '", null, null, true);});</script>' +
templates['footer']
);
});
});
app.get('/search', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("search", null, "search") + templates['footer']);
});
});
app.get('/search/:term', function(req, res) {
app.build_header({ req: req, res: res }, function(err, header) {
res.send(header + app.create_route("search/"+req.params.term, null, "search") + templates['footer']);
});
});
app.get('/reindex', function(req, res) {
topics.reIndexAll(function(err) {
if(err) {
res.json(err);
} else {
res.send('All topics reindexed');
}
});
});
});
// These functions are called via ajax once the initial page is loaded to populate templates with data
function api_method(req, res) {
var uid = (req.user) ? req.user.uid : 0;
switch(req.params.method) {
case 'get_templates_listing' :
utils.walk(global.configuration.ROOT_DIRECTORY + '/public/templates', function(err, data) {
res.json(data);
});
break;
case 'home' :
categories.getAllCategories(function(data) {
var async = require('async');
function iterator(category, callback) {
categories.getRecentReplies(category.cid, 2, function(posts) {
category["posts"] = posts;
category["post_count"] = posts.length>2 ? 2 : posts.length;
callback(null);
});
}
async.each(data.categories, iterator, function(err) {
data.motd_class = (config.show_motd === '1' || config.show_motd === undefined) ? '' : 'none';
data.motd = marked(config.motd || "# NodeBB v" + pkg.version + "\nWelcome to NodeBB, the discussion platform of the future.\n\n<a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-large\"><i class=\"icon-comment\"></i> Get NodeBB</a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-large\"><i class=\"icon-github-alt\"></i> Fork us on Github</a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-large\"><i class=\"icon-twitter\"></i> @dcplabs</a>");
res.json(data);
});
}, uid);
break;
case 'login' :
var data = {},
login_strategies = auth.get_login_strategies(),
num_strategies = login_strategies.length;
if (num_strategies == 0) {
data = {
'login_window:spansize': 'span12',
'alternate_logins:display': 'none'
};
} else {
data = {
'login_window:spansize': 'span6',
'alternate_logins:display': 'block'
}
for (var i=0, ii=num_strategies; i<ii; i++) {
data[login_strategies[i] + ':display'] = 'active';
}
}
data.token = res.locals.csrf_token;
res.json(data);
break;
case 'register' :
var data = {},
login_strategies = auth.get_login_strategies(),
num_strategies = login_strategies.length;
if (num_strategies == 0) {
data = {
'register_window:spansize': 'span12',
'alternate_logins:display': 'none'
};
} else {
data = {
'register_window:spansize': 'span6',
'alternate_logins:display': 'block'
}
for (var i=0, ii=num_strategies; i<ii; i++) {
data[login_strategies[i] + ':display'] = 'active';
}
}
data.token = res.locals.csrf_token;
res.json(data);
break;
case 'topic' :
topics.getTopicWithPosts(req.params.id, uid, function(err, data) {
res.json(data);
});
break;
case 'category' :
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' :
categories.getLatestTopics(uid, 0, 9, function(data) {
res.json(data);
});
break;
case 'popular' :
categories.getLatestTopics(uid, 0, 9, function(data) {
res.json(data);
});
break;
case 'active' :
categories.getLatestTopics(uid, 0, 9, function(data) {
res.json(data);
});
break;
case 'confirm':
user.email.confirm(req.params.id, function(data) {
if (data.status === 'ok') {
res.json({
'alert-class': 'alert-success',
title: 'Email Confirmed',
text: 'Thank you for vaidating your email. Your account is now fully activated.'
});
} else {
res.json({
'alert-class': 'alert-error',
title: 'An error occurred...',
text: 'There was a problem validating your email address. Perhaps the code was invalid or has expired.'
});
}
});
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

@@ -16,17 +16,19 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
async = require('async'),
RedisStoreLib = require('connect-redis')(express),
redis = require('redis'),
redisServer = redis.createClient(global.nconf.get('redis:port'), global.nconf.get('redis:host')),
redisServer = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host')),
RedisStore = new RedisStoreLib({
client: redisServer,
ttl: 60*60*24*14
}),
socketCookieParser = express.cookieParser(global.nconf.get('secret')),
socketCookieParser = express.cookieParser(nconf.get('secret')),
admin = {
'categories': require('./admin/categories.js'),
'user': require('./admin/user.js')
};
},
plugins = require('./plugins'),
winston = require('winston');
(function(io) {
var users = {},
userSockets = {},
@@ -47,22 +49,22 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
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) {
user.getUserField(uid, 'username', function(err, username) {
socket.emit('event:connect', {status: 1, username:username});
});
}
});
});
socket.on('disconnect', function() {
var index = userSockets[uid].indexOf(socket);
if(index !== -1) {
userSockets[uid].splice(index, 1);
@@ -72,17 +74,17 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
delete users[sessionID];
if(uid)
io.sockets.in('global').emit('api:user.isOnline', isUserOnline(uid));
}
}
for(var roomName in rooms) {
socket.leave(roomName);
if(rooms[roomName][socket.id]) {
delete rooms[roomName][socket.id];
}
updateRoomBrowsingText(roomName);
}
updateRoomBrowsingText(roomName);
}
});
@@ -104,7 +106,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
function getAnonymousCount(roomName) {
var clients = io.sockets.clients(roomName);
var anonCount = 0;
for(var i=0; i<clients.length; ++i) {
var hs = clients[i].handshake;
@@ -112,11 +114,11 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
++anonCount;
}
}
return anonCount;
return anonCount;
}
var uids = getUidsInRoom(rooms[roomName]);
var anonymousCount = getAnonymousCount(roomName);
function userList(users, anonymousCount, userCount) {
@@ -125,8 +127,8 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
for (var i = 0, ii=users.length; i<ii; ++i) {
usernames[i] = '<strong>' + '<a href="/users/'+users[i].userslug+'">' + users[i].username + '</a></strong>';
}
var joiner = anonymousCount + userCount == 1 ? 'is' : 'are',
var joiner = anonymousCount + userCount == 1 ? 'is' : 'are',
userList = anonymousCount > 0 ? usernames.concat(util.format('%d guest%s', anonymousCount, anonymousCount > 1 ? 's' : '')) : usernames,
lastUser = userList.length > 1 ? ' and ' + userList.pop() : '';
@@ -144,18 +146,18 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}
socket.on('event:enter_room', function(data) {
if (data.leave !== null) {
socket.leave(data.leave);
}
socket.join(data.enter);
rooms[data.enter] = rooms[data.enter] || {};
if (uid) {
rooms[data.enter][socket.id] = uid;
if (data.leave && rooms[data.leave] && rooms[data.leave][socket.id]) {
delete rooms[data.leave][socket.id];
}
@@ -166,18 +168,20 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
updateRoomBrowsingText(data.enter);
if (data.enter != 'admin')
if (data.enter != 'admin')
io.sockets.in('admin').emit('api:get_all_rooms', io.sockets.manager.rooms);
});
// BEGIN: API calls (todo: organize)
socket.on('api:updateHeader', function(data) {
if(uid) {
user.getUserFields(uid, data.fields, function(fields) {
fields.uid = uid;
socket.emit('api:updateHeader', fields);
user.getUserFields(uid, data.fields, function(err, fields) {
if(!err && fields) {
fields.uid = uid;
socket.emit('api:updateHeader', fields);
}
});
}
else {
@@ -185,12 +189,12 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
uid:0,
username: "Anonymous User",
email: '',
picture: require('gravatar').url('', {s:'24'}, https=global.nconf.get('https'))
picture: require('gravatar').url('', {s:'24'}, https=nconf.get('https'))
});
}
});
socket.on('user.exists', function(data) {
user.exists(utils.slugify(data.username), function(exists){
socket.emit('user.exists', {exists: exists});
@@ -231,12 +235,12 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.on('api:user.get_online_users', function(data) {
var returnData = [];
for(var i=0; i<data.length; ++i) {
var uid = data[i];
if(isUserOnline(uid))
returnData.push(uid);
else
else
returnData.push(0);
}
socket.emit('api:user.get_online_users', returnData);
@@ -246,100 +250,156 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
socket.emit('api:user.isOnline', isUserOnline(uid));
});
socket.on('api:user.changePassword', function(data) {
user.changePassword(socket, uid, data, function(success) {
if(success) {
socket.emit('api:user.changePassword');
}
});
});
socket.on('api:user.updateProfile', function(data) {
user.updateProfile(socket, uid, data);
socket.on('api:user.changePassword', function(data, callback) {
user.changePassword(uid, data, callback);
});
socket.on('api:user.changePicture', function(data) {
socket.on('api:user.updateProfile', function(data, callback) {
user.updateProfile(uid, data, callback);
});
socket.on('api:user.changePicture', function(data, callback) {
var type = data.type;
function updateHeader() {
user.getUserFields(uid, ['picture'], function(fields) {
fields.uid = uid;
socket.emit('api:updateHeader', fields);
user.getUserFields(uid, ['picture'], function(err, fields) {
if(!err && fields) {
fields.uid = uid;
socket.emit('api:updateHeader', fields);
callback(true);
} else {
callback(false);
}
});
}
if(type === 'gravatar') {
user.getUserField(uid, 'gravatarpicture', function(gravatar) {
if(type === 'gravatar') {
user.getUserField(uid, 'gravatarpicture', function(err, gravatar) {
user.setUserField(uid, 'picture', gravatar);
updateHeader();
});
}
else if(type === 'uploaded') {
user.getUserField(uid, 'uploadedpicture', function(uploadedpicture) {
} else if(type === 'uploaded') {
user.getUserField(uid, 'uploadedpicture', function(err, uploadedpicture) {
user.setUserField(uid, 'picture', uploadedpicture);
updateHeader();
});
}
});
socket.on('api:user.follow', function(data) {
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
});
});
}
});
} else {
callback(false);
}
});
socket.on('api:user.unfollow', function(data) {
socket.on('api:user.follow', function(data, callback) {
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
});
});
}
});
user.follow(uid, data.uid, callback);
}
});
socket.on('api:user.saveSettings', function(data) {
socket.on('api:user.unfollow', function(data, callback) {
if(uid) {
user.unfollow(uid, data.uid, callback);
}
});
socket.on('api:user.saveSettings', function(data, callback) {
if(uid) {
user.setUserFields(uid, {
showemail:data.showemail
});
socket.emit('event:alert', {
title: 'Saved',
message: 'Settings saved!',
type: 'success',
timeout: 2000
});
callback(true);
}
});
socket.on('api:topics.post', function(data) {
topics.post(socket, uid, data.title, data.content, data.category_id, data.images);
topics.post(uid, data.title, data.content, data.category_id, data.images, function(err, result) {
if(err) {
if(err.message === 'not-logged-in') {
socket.emit('event:alert', {
title: 'Thank you for posting',
message: 'Since you are unregistered, your post is awaiting approval. Click here to register now.',
type: 'warning',
timeout: 7500,
clickfn: function() {
ajaxify.go('register');
}
});
} else if(err.message === 'title-too-short') {
topics.emitTitleTooShortAlert(socket);
} else if(err.message === 'content-too-short') {
posts.emitContentTooShortAlert(socket);
} else if (err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket);
}
return;
}
if(result) {
posts.getTopicPostStats(socket);
socket.emit('event:alert', {
title: 'Thank you for posting',
message: 'You have successfully posted. Click here to view your post.',
type: 'notify',
timeout: 2000
});
}
});
});
socket.on('api:topics.markAllRead', function(data, callback) {
topics.markAllRead(uid, function(err, success) {
if(!err && success) {
callback(true);
} else {
callback(false);
}
});
});
socket.on('api:posts.reply', function(data) {
posts.reply(socket, data.topic_id, uid, data.content, data.images);
if(uid < 1) {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'You don&apos;t seem to be logged in, so you cannot reply.',
type: 'error',
timeout: 2000
});
return;
}
posts.reply(data.topic_id, uid, data.content, data.images, function(err, result) {
if(err) {
if(err.message === 'content-too-short') {
posts.emitContentTooShortAlert(socket);
} else if(err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket);
} else if(err.message === 'reply-error') {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'Your reply could not be posted at this time. Please try again later.',
type: 'notify',
timeout: 2000
});
}
return;
}
if(result) {
posts.getTopicPostStats(socket);
socket.emit('event:alert', {
title: 'Reply Successful',
message: 'You have successfully replied. Click here to view your reply.',
type: 'notify',
timeout: 2000
});
}
});
});
socket.on('api:user.active.get', function() {
@@ -409,7 +469,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
if(!data.title || data.title.length < topics.minimumTitleLength) {
topics.emitTitleTooShortAlert(socket);
return;
} else if (!data.content || data.content.length < posts.minimumPostLength) {
} else if (!data.content || data.content.length < require('../public/config.json').minimumPostLength) {
posts.emitContentTooShortAlert(socket);
return;
}
@@ -424,70 +484,83 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
postTools.restore(uid, data.pid);
});
socket.on('api:notifications.get', function(data) {
socket.on('api:notifications.get', function(data, callback) {
user.notifications.get(uid, function(notifs) {
socket.emit('api:notifications.get', notifs);
callback(notifs);
});
});
socket.on('api:notifications.hasFlag', function(data) {
user.notifications.hasFlag(uid, function(flag) {
socket.emit('api:notifications.hasFlag', flag);
});
});
socket.on('api:notifications.removeFlag', function() {
user.notifications.removeFlag(uid);
});
socket.on('api:notifications.mark_read', function(nid) {
notifications.mark_read(nid, uid);
});
socket.on('api:notifications.mark_all_read', function(data, callback) {
notifications.mark_all_read(uid, function(err) {
if (!err) callback();
});
});
socket.on('api:categories.getRecentReplies', function(tid) {
categories.getRecentReplies(tid, 4, function(replies) {
socket.emit('api:categories.getRecentReplies', replies);
});
});
socket.on('getChatMessages', function(data, callback) {
var touid = data.touid;
require('./messaging').getMessages(uid, touid, function(err, messages) {
if(err)
return callback(null);
callback(messages);
});
});
socket.on('sendChatMessage', function(data) {
var touid = data.touid;
if(touid === uid || uid === 0) {
return;
}
if(userSockets[touid]) {
var msg = utils.strip_tags(data.message),
numSockets = userSockets[touid].length;
user.getUserField(uid, 'username', function(username) {
user.getUserField(uid, 'username', function(err, username) {
var finalMessage = username + ' says : ' + msg;
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.create(finalMessage, 5, '#', 'notification_' + uid + '_' + touid, function(nid) {
notifications.push(nid, [touid], function(success) {
});
});
require('./messaging').addMessage(uid, touid, msg, function(err, message) {
});
});
}
});
socket.on('api:config.get', function(data) {
meta.config.get(function(config) {
meta.configs.get(function(config) {
socket.emit('api:config.get', config);
});
});
socket.on('api:config.set', function(data) {
meta.config.set(data.key, data.value, function(err) {
meta.configs.set(data.key, data.value, function(err) {
if (!err) socket.emit('api:config.set', { status: 'ok' });
});
});
socket.on('api:config.remove', function(key) {
meta.config.remove(key);
meta.configs.remove(key);
});
socket.on('api:composer.push', function(data) {
@@ -495,7 +568,7 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
if (parseInt(data.tid) > 0) {
topics.getTopicData(data.tid, function(topicData) {
if (data.body)
if (data.body)
topicData.body = data.body;
socket.emit('api:composer.push', {
@@ -505,14 +578,16 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
});
});
} else if (parseInt(data.cid) > 0) {
user.getUserFields(uid, ['username', 'picture'], function(userData) {
socket.emit('api:composer.push', {
tid: 0,
cid: data.cid,
username: userData.username,
picture: userData.picture,
title: undefined
});
user.getUserFields(uid, ['username', 'picture'], function(err, userData) {
if(!err && userData) {
socket.emit('api:composer.push', {
tid: 0,
cid: data.cid,
username: userData.username,
picture: userData.picture,
title: undefined
});
}
});
} else if (parseInt(data.pid) > 0) {
async.parallel([
@@ -577,33 +652,71 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}
});
socket.on('api:topic.loadMore', function(data) {
var start = data.after,
end = start + 10;
socket.on('api:topic.loadMore', function(data, callback) {
var start = data.after,
end = start + 9;
topics.getTopicPosts(data.tid, start, end, uid, function(posts) {
io.sockets.in('topic_' + data.tid).emit('event:new_post', {
posts: posts
});
callback({posts:posts});
});
});
socket.on('api:admin.topics.getMore', function(data) {
socket.on('api:category.loadMore', function(data, callback) {
var start = data.after,
end = start + 9;
categories.getCategoryTopics(data.cid, start, end, uid, function(topics) {
callback({topics:topics});
});
});
socket.on('api:topics.loadMoreRecentTopics', function(data, callback) {
var start = data.after,
end = start + 9;
topics.getLatestTopics(uid, start, end, function(latestTopics) {
callback(latestTopics);
});
});
socket.on('api:topics.loadMoreUnreadTopics', function(data, callback) {
var start = data.after,
end = start + 9;
topics.getUnreadTopics(uid, start, end, function(unreadTopics) {
callback(unreadTopics);
});
});
socket.on('api:users.loadMore', function(data, callback) {
var start = data.after,
end = start + 19;
user.getUsers(data.set, start, end, function(err, data) {
if(err) {
winston.err(err);
} else {
callback({users:data});
}
});
});
socket.on('api:admin.topics.getMore', function(data, callback) {
topics.getAllTopics(data.limit, data.after, function(topics) {
socket.emit('api:admin.topics.getMore', JSON.stringify(topics));
callback(JSON.stringify(topics));
});
});
socket.on('api:admin.categories.update', function(data) {
admin.categories.update(data, socket);
});
socket.on('api:admin.user.makeAdmin', function(theirid) {
if(uid && uid > 0) {
admin.user.makeAdmin(uid, theirid, socket);
}
});
socket.on('api:admin.user.removeAdmin', function(theirid) {
if(uid && uid > 0) {
admin.user.removeAdmin(uid, theirid, socket);
@@ -616,21 +729,45 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }),
}
});
socket.on('api:admin.user.banUser', function(theirid) {
if(uid && uid > 0) {
admin.user.banUser(uid, theirid, socket);
}
});
socket.on('api:admin.user.unbanUser', function(theirid) {
if(uid && uid > 0) {
admin.user.unbanUser(uid, theirid, socket);
}
});
socket.on('api:admin.user.search', function(username) {
if(uid && uid > 0) {
user.search(username, function(data) {
socket.emit('api:admin.user.search', data);
});
}
else
} else {
socket.emit('api:admin.user.search', null);
}
});
socket.on('api:admin:themes.getInstalled', function() {
socket.on('api:admin.themes.getInstalled', function(callback) {
meta.themes.get(function(err, themeArr) {
socket.emit('api:admin:themes.getInstalled', themeArr);
callback(themeArr);
});
});
socket.on('api:admin.plugins.toggle', function(plugin_id) {
plugins.toggleActive(plugin_id, function(status) {
socket.emit('api:admin.plugins.toggle', status);
});
});
socket.on('api:meta.buildTitle', function(text, callback) {
meta.title.build(text, uid, function(err, title, numNotifications) {
callback(title, numNotifications);
});
});
});
}(SocketIO));