Compare commits

..

460 Commits

Author SHA1 Message Date
Julian Lam
48a7c48f7b 0.0.7 2013-09-30 17:05:28 -04:00
Julian Lam
7a919fbac4 dropping down to 0.0.6 for re-release 2013-09-30 17:05:22 -04:00
Julian Lam
181220621e fixed issue with server crashing on post 2013-09-30 17:01:39 -04:00
Julian Lam
249c45dfe2 0.0.7 2013-09-30 16:30:11 -04:00
Julian Lam
b19d84f1a7 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-30 16:28:50 -04:00
Julian Lam
865edb70c2 added meta description to topics, closes #362 2013-09-30 16:28:37 -04:00
Baris Usakli
e78369f0fa use User.isAdmin instead 2013-09-30 16:24:00 -04:00
Baris Usakli
d40a6a5c3f added adminitstrator value to userData for admin users page 2013-09-30 16:19:20 -04:00
Baris Usakli
cb7768d095 cant ban yourself 2013-09-30 16:04:54 -04:00
Baris Usakli
4290516d29 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-30 16:03:06 -04:00
Baris Usakli
6a7a2301ee removed admin and delete buttons from template 2013-09-30 16:03:00 -04:00
Julian Lam
9a79c58e33 closed #361 - groups admin modal had an extra button that did nothing 2013-09-30 16:02:38 -04:00
Julian Lam
4c39c1ec30 fixed issue with install script erroring out if redis host is not defined 2013-09-30 15:52:10 -04:00
psychobunny
571cb8b44f readded back admin css for users 2013-09-30 15:31:29 -04:00
Baris Usakli
4207792ffd added app alert if template data cant be loaded 2013-09-30 15:12:14 -04:00
Baris Usakli
9f67282a79 admin site will display reconnecting notification when server is offline closes #346 2013-09-30 13:04:03 -04:00
Julian Lam
ee71c1cf0d Merge branch 'iamcardinal-merge' 2013-09-30 11:23:41 -04:00
Julian Lam
06f2284bcd added output to show bound address on instance start 2013-09-30 11:23:11 -04:00
Julian Lam
8cbe79655a emptying Meta.config on init (Just to be safe) 2013-09-30 11:18:02 -04:00
Julian Lam
a4c1d733b7 fixed issue where setOnEmpty would fail in install.js as Meta.config was
not defined (as it had not been initted yet)
2013-09-30 11:17:12 -04:00
Julian Lam
839649d42f Merge branch 'master' of https://github.com/iamcardinal/NodeBB-enhancement into iamcardinal-merge 2013-09-30 11:12:38 -04:00
Quinton Marchi
504e2aac4a Adds: regex for ports, mini for public/src/forum
+ Regular expression for port during install
+ Minification for public/src/forum JS files
+ Minification for public/src/forum/admin JS files
2013-09-30 02:42:34 -04:00
Minami
eafb41602c Edited to simplify install
Removed the two steps and made it default to 0.0.0.0, edited the
webserver.js to reflect the changes.
2013-09-29 21:03:29 -05:00
Baris Soner Usakli
2dcc4172c4 closes #357 2013-09-29 21:29:43 -04:00
Baris Soner Usakli
f96a711298 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-29 21:03:19 -04:00
Baris Soner Usakli
ad28b9b339 fixed crash in meta 2013-09-29 21:03:16 -04:00
Julian Lam
8dd8536f6b closes #317 - hitting discard on a post with content will now throw a
bootbox confirmation modal
2013-09-29 20:38:45 -04:00
Julian Lam
7bf5b2ec57 changed fireHook to be error-first, closes #319 2013-09-29 20:33:35 -04:00
Julian Lam
aa731aa894 Merge branch 'middleware_refactor' 2013-09-29 20:28:50 -04:00
Julian Lam
c58cb257dc closes #349 - loading middlewares using async instead of crazy
middleware-stack modifying shenanigans
2013-09-29 20:27:52 -04:00
Baris Soner Usakli
06f59cf853 closes #310 2013-09-29 15:08:00 -04:00
Baris Soner Usakli
83b4b5434f fixed username button title 2013-09-29 14:27:31 -04:00
Baris Soner Usakli
9de5214a2f closes #353 2013-09-29 14:18:43 -04:00
Julian Lam
f08067bab2 fixes #356 - issue where accessing a NodeBB using a different machine name
from one that it was set up with causing template data to not load, as it
was being accessed via absolute url, instead of relative url
2013-09-29 13:47:27 -04:00
Julian Lam
1160f39cb0 revamped reconnection notification, closes #354 2013-09-28 22:05:10 -04:00
Julian Lam
d633ecd160 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-28 17:45:27 -04:00
Julian Lam
4cea313060 updated getPostsByPids method to resolve premature return issue, and
refactored it to use async.map instead of eachSeries
2013-09-28 17:44:18 -04:00
Baris Soner Usakli
9b7f8076eb removed console.log 2013-09-28 16:18:55 -04:00
Quinton Marchi
571259f241 Adds hostname support, fixes #343
Works from previous testing but I haven't tried this personal revision
of the code. Take it with a grain of salt.
2013-09-28 03:55:06 -04:00
psychobunny
607ee8bbc1 added outgoing.less, fixed modern.less theme, fixed #316 2013-09-27 13:11:01 -04:00
psychobunny
36d2f74887 fixed unread category view 2013-09-27 12:59:36 -04:00
Julian Lam
cdaf409a99 monkey-patching getPostsByPids so that it returns ISOString instead of relativeTime 2013-09-27 11:53:25 -04:00
Julian Lam
fba487d8a2 removing extra comma in package.json 2013-09-27 11:48:01 -04:00
Julian Lam
3ab7306199 linting, minor bugfix, moving hiredis to optional deps 2013-09-27 11:47:16 -04:00
Julian Lam
2e4e94d5f8 Merge branch 'redis-optimizations' of https://github.com/adarqui/NodeBB into adarqui-redis-optimizations 2013-09-27 11:44:40 -04:00
Baris Usakli
3348ed3524 fixed email key 2013-09-26 13:56:36 -04:00
Baris Usakli
942a21b4be fixed email meta changes 2013-09-26 13:31:38 -04:00
Baris Usakli
9c14618d55 dont redirect back to reset password after login 2013-09-26 12:50:24 -04:00
Baris Usakli
7d50551392 remove throw in email send 2013-09-26 12:29:35 -04:00
Baris Usakli
f2c1a92513 closes #215 2013-09-25 13:08:11 -04:00
Baris Usakli
fe6595cd2a Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-25 12:30:17 -04:00
Baris Usakli
c2aac916aa fixed topics not appearing after a post in category view 2013-09-25 12:30:07 -04:00
psychobunny
e161b5387b fixed untranslated language on dynamic new posts and infinite loading 2013-09-25 12:16:06 -04:00
Baris Usakli
585e5cd88f closes #341 2013-09-25 12:11:32 -04:00
psychobunny
1637ffc5dc re-adding teaser 2013-09-25 11:40:05 -04:00
psychobunny
a28797ee03 fix for recent view teasers 2013-09-25 11:26:28 -04:00
Julian Lam
49e28f9d1e fixing bug where outgoing links didn't actually let you leave... oops! 2013-09-25 11:25:48 -04:00
Baris Usakli
4a7cd664fd fixed a bug in template parsing 2013-09-24 16:46:45 -04:00
Baris Usakli
253e11d55b cleaned up websockets.js 2013-09-24 16:02:14 -04:00
Baris Usakli
eb1c1c78d4 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-24 15:59:51 -04:00
Baris Usakli
0b0d64b52e logout/login changes, updateHeader changes 2013-09-24 15:59:08 -04:00
Julian Lam
1ae51ef5ea redis export function (commented out for now) 2013-09-24 15:36:17 -04:00
psychobunny
de1e3230f5 fixed topics and admin views, broken after merge 2013-09-24 15:23:16 -04:00
psychobunny
1fbc038e64 fixed conflicts, added new language key for users/online 2013-09-24 15:15:39 -04:00
psychobunny
cd63dd429b finalizing todo's before merging 2013-09-24 14:59:13 -04:00
Baris Usakli
fbfdf561fc Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-24 14:57:01 -04:00
Baris Usakli
83d5a84edd login redirects back 2013-09-24 14:56:51 -04:00
psychobunny
5abf02c6d1 default language, en 2013-09-24 14:55:46 -04:00
psychobunny
b8037845d6 registration.tpl; translator variable system 2013-09-24 14:50:38 -04:00
Julian Lam
c80e2552b2 express-namespaceing routes 2013-09-24 14:18:41 -04:00
psychobunny
1a1fea535b header language 2013-09-24 14:17:36 -04:00
psychobunny
5c7da4b686 fixed serverside templating; footer, logout internationalization; updated more global lang keys 2013-09-24 14:14:26 -04:00
Julian Lam
b6ee89a6d8 fixed typo in plugins page 2013-09-24 14:12:26 -04:00
psychobunny
2d8e6bd980 category language 2013-09-24 13:31:44 -04:00
Julian Lam
a173d61464 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-24 13:31:10 -04:00
Baris Usakli
8f04a136c8 fixed whitespace 2013-09-24 13:31:02 -04:00
Julian Lam
b7675e1ec7 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-24 13:30:59 -04:00
Julian Lam
964fbfe2bb fixes to get nodebb to at least load on IE 2013-09-24 13:30:54 -04:00
Baris Usakli
8fb8956c0d closes #331 2013-09-24 13:30:09 -04:00
psychobunny
c11920ac08 internationalization: 404 and 403 2013-09-24 13:22:10 -04:00
Baris Usakli
9a4eb26246 closes #332 2013-09-24 13:06:14 -04:00
Julian Lam
0d9958afe7 HOTFIX for scripts 2013-09-24 13:01:18 -04:00
Julian Lam
d6dd74b50a removed extra debug log 2013-09-24 12:34:54 -04:00
Julian Lam
d5437ca8fa Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-24 12:34:17 -04:00
Julian Lam
14720057c2 serving unminified libraries in development mode, minified otherwise 2013-09-24 12:33:51 -04:00
Baris Usakli
170ed8cc01 fixed conflict 2013-09-24 12:31:28 -04:00
Baris Usakli
25576eb35a closes #329 2013-09-24 12:29:51 -04:00
Julian Lam
66cb1fb6ad removed console.log from socket connection success handler 2013-09-24 11:02:06 -04:00
Julian Lam
bcc65fd879 closed #328 2013-09-24 08:38:19 -04:00
Julian Lam
99440585e6 Merge branch 'master' into optimize-22-09-2013
Conflicts:
	package.json
2013-09-23 20:04:09 -04:00
Baris Usakli
290d69d14a remember where the topic was left off 2013-09-23 17:41:54 -04:00
Baris Usakli
4fce06677f Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-23 16:49:11 -04:00
Baris Usakli
f53cd8b5b6 category view fix 2013-09-23 16:49:01 -04:00
Julian Lam
da59f47624 adding missing files 2013-09-23 15:46:40 -04:00
Julian Lam
521586f08f Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-23 15:45:23 -04:00
Julian Lam
f39a7ada7c skipping minification if the minFile is already up-to-date - issue #322 2013-09-23 15:45:03 -04:00
Baris Usakli
bae9f46d8b small fixes to search 2013-09-23 15:31:52 -04:00
Julian Lam
d7ea24e218 issue #322 - minifying css file and added jquery to minified compilation 2013-09-23 15:27:46 -04:00
Julian Lam
e621d7e601 closed #322 - autominifying client-side assets on load 2013-09-23 15:21:34 -04:00
Baris Usakli
aa6eff4c54 one more itme 2013-09-23 14:58:06 -04:00
Baris Usakli
f36583e676 possible fix for users/online duplication 2013-09-23 14:55:34 -04:00
Baris Usakli
0a0a91d4b2 remove listener 2013-09-23 14:51:59 -04:00
Baris Usakli
6c70f9e308 only collapse header if its expanded 2013-09-23 14:51:16 -04:00
Baris Usakli
b25c3d8b67 closes #324 2013-09-23 14:40:31 -04:00
Baris Usakli
bade99d069 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-23 13:43:25 -04:00
Baris Usakli
776b51fef7 closes #323 2013-09-23 13:43:15 -04:00
Julian Lam
da2d014f00 added IRC link to wiki 2013-09-23 12:55:21 -04:00
Julian Lam
7fdf83089d linting 2013-09-23 12:50:27 -04:00
Julian Lam
043aafd7b7 closed #315 2013-09-22 20:40:10 -04:00
Julian Lam
3254979568 re: #321, removed cheerio dep from package.json 2013-09-22 15:28:22 -04:00
Julian Lam
6fd202fe36 closed #321, removed cheerio use altogether, and made changes to the
client side to ajaxify the user to /outgoing when an external link is
clicked
2013-09-22 15:27:10 -04:00
Julian Lam
5f00f1e18e renamed "toHTML" method to "parse" 2013-09-22 13:54:21 -04:00
Julian Lam
c940ce3329 camelCased get_latest_undeleted_pid method 2013-09-22 13:33:05 -04:00
Julian Lam
2366e2b209 refactored get_latest_undeleted_pid to not blindly call getPostsByTid in
order to determine post deletion. (issue #320)
2013-09-22 13:28:10 -04:00
Julian Lam
2b7fd3c9d1 interim commit 2013-09-22 12:55:03 -04:00
Andrew Darqui
d3e37d1716 slight variable name changes & remove unused var from getPostsByPids, removed underscore dependency 2013-09-22 00:36:56 -04:00
Andrew Darqui
d2b858c997 slight variable name changes & remove unused var from getPostsByPids, removed underscore dependency 2013-09-22 00:36:40 -04:00
Baris Soner Usakli
de34c7580f closes #314 2013-09-22 00:29:31 -04:00
Baris Soner Usakli
bcb492751c closes #313, also fixed progressbar 2013-09-22 00:01:31 -04:00
adarqui
e26cc79819 redis multi pipeline used for getPostsByPids 2013-09-21 22:47:27 -04:00
adarqui
98cb2d4c17 a little logic to allow nodebb to use a unix domain socket rather than tcp/ip. unix dom sock has less overhead/latency. 2013-09-21 15:42:54 -04:00
adarqui
2fc569715c hiredis & underscore packages 2013-09-21 15:26:52 -04:00
Julian Lam
3eb594df43 fixing timestamps in category view (topics listing) for noscript 2013-09-20 20:48:14 -04:00
Julian Lam
8243019a60 closed #312 2013-09-20 20:43:02 -04:00
psychobunny
5a10f7fcfe init variables in translator 2013-09-20 17:31:53 -04:00
psychobunny
5e89caf358 internationalization: topic.json 2013-09-20 17:16:01 -04:00
psychobunny
35b40ef650 internationalization: unread.tpl 2013-09-20 17:03:30 -04:00
psychobunny
56b7d6cb7c internationalization: users 2013-09-20 17:00:33 -04:00
psychobunny
8b8a890ac9 removed unnecessary warning message to anonymous users attempting to unfavourite posts 2013-09-20 16:57:25 -04:00
psychobunny
5645bcee2d topic.json en lang; tested serverside internationalization in favourites; fixed some keys in login 2013-09-20 16:43:50 -04:00
psychobunny
a631707db4 app.js + todo 2013-09-20 16:02:25 -04:00
psychobunny
451ffafb9e finished initial client side & server side language parsing methods; integrated preloading into ajaxify and server app.js 2013-09-20 16:01:52 -04:00
psychobunny
b5274a0d91 init 2013-09-20 14:35:32 -04:00
Baris Soner Usakli
db4dc03abc added timeago to unread page 2013-09-19 20:49:52 -04:00
Baris Soner Usakli
6be5bcc4c8 closes #308 2013-09-19 20:43:56 -04:00
Baris Usakli
21efda4a84 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-19 16:40:11 -04:00
Baris Usakli
082375f129 link fade outs 2013-09-19 16:40:02 -04:00
Julian Lam
1204859263 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-19 16:38:20 -04:00
Julian Lam
c8b5d1561e minor CSS fixes to post timeago text in topic view 2013-09-19 16:38:04 -04:00
Baris Usakli
6912e77d25 removed data-pid from buttons 2013-09-19 16:25:51 -04:00
Baris Usakli
d84d096e7e added post links 2013-09-19 16:24:30 -04:00
Baris Usakli
29c17fd32e fixed user infinite loading 2013-09-19 15:50:28 -04:00
Baris Usakli
d721320af5 added jquery.timeago.js 2013-09-19 15:10:03 -04:00
Baris Usakli
efef9c864c closes #306 2013-09-19 15:02:35 -04:00
Baris Usakli
27cb837b08 closes #303 2013-09-19 12:29:25 -04:00
psychobunny
047fcbe63f style revert for cerulean 2013-09-18 17:06:32 -04:00
psychobunny
dbe0b1551b creating a separate theme for my metro ui shenanigans, follow up revert on cerulean incoming 2013-09-18 17:02:26 -04:00
psychobunny
8f7b047b7a fixed padding in category-box 2013-09-18 16:58:12 -04:00
psychobunny
a38ace0df9 moved metro slide animation out of vanilla into cerulean, and rejigged the home page a bit 2013-09-18 16:56:34 -04:00
psychobunny
0b0b06e8b9 fixed users.tpl from fail merge earlier 2013-09-18 16:31:02 -04:00
psychobunny
f83b61903a screwing around with animations in cerulean; started modernui look and feel 2013-09-18 16:25:32 -04:00
Julian Lam
881fa41bb7 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-18 16:25:23 -04:00
Baris Usakli
f9442db96f closes #284 2013-09-18 16:23:08 -04:00
Julian Lam
64521494ce removed outdated socket.io client libs (socket.io package adds these routes anyway) 2013-09-18 16:22:35 -04:00
Julian Lam
e75e3399ab added link to upgrade instructions 2013-09-18 16:06:51 -04:00
Baris Usakli
e5ef498164 merged users.tpl 2013-09-18 16:05:01 -04:00
Baris Usakli
24e4be77d8 closes #304 2013-09-18 16:01:54 -04:00
psychobunny
0d55f2ef32 cerulean: switched to lucida grande / tahoma 2013-09-18 14:47:38 -04:00
psychobunny
8ec3371139 cerulean theme: new users view 2013-09-18 12:45:31 -04:00
Julian Lam
ccca4d2914 fixed issue where meta was called before nconf loaded 2013-09-18 12:15:29 -04:00
psychobunny
28704a6164 hinting 2013-09-18 10:50:02 -04:00
psychobunny
cf4ba9d1d3 format/jshint favourites.js 2013-09-18 10:44:54 -04:00
Julian Lam
c69e30c146 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-18 07:23:50 -04:00
psychobunny
fd32d75d3b jshinted categories.js 2013-09-17 15:54:08 -04:00
psychobunny
afa228fd91 jshint: temporarily setting unused flag to false, low priority right now compared to the rest of our issues 2013-09-17 15:49:07 -04:00
psychobunny
a01a7ae5fc fixed following.tpl and followers.tpl layout (broken from BS3 merge) 2013-09-17 15:46:22 -04:00
psychobunny
64b071f277 nconf and redis wrapper no longer global objects. jshinted app.js. updated sockets.io to latest 2013-09-17 15:38:57 -04:00
Julian Lam
af246ad0d7 Merge remote-tracking branch 'origin' 2013-09-17 15:14:15 -04:00
Julian Lam
56a87329ec removing extra line 2013-09-17 15:14:11 -04:00
Baris Usakli
eddddc694d closes #302 2013-09-17 14:37:01 -04:00
Baris Usakli
ce61138351 fixed conflicts 2013-09-17 13:42:07 -04:00
Baris Usakli
564662ee00 closes #294 2013-09-17 13:37:03 -04:00
psychobunny
678db837fd refactored post-block 2013-09-17 13:29:45 -04:00
psychobunny
c789f4230d formatted app.js, updated jshintrc to include node 2013-09-17 13:15:36 -04:00
psychobunny
1b9e451a6f formatting - server side admin 2013-09-17 13:10:58 -04:00
psychobunny
1780b343b4 formatting - routes 2013-09-17 13:10:14 -04:00
psychobunny
aea3181d27 formatting - server side core 2013-09-17 13:09:37 -04:00
psychobunny
c44461e33f formatting - template js admin side 2013-09-17 13:07:30 -04:00
psychobunny
221b9bc149 formatting - template associated js 2013-09-17 13:05:54 -04:00
psychobunny
dc41c6bc0d formatting - core client side 2013-09-17 13:04:40 -04:00
psychobunny
828d937dec .jsbeautify 2013-09-17 13:03:53 -04:00
psychobunny
cf889cfe25 .jshintrc, massive dehinting adventure to follow 2013-09-17 12:59:04 -04:00
psychobunny
6bab2523d5 fixes #276 popup alert appearing under admin content 2013-09-17 12:55:20 -04:00
psychobunny
d714b5c592 editorconfig end_of_line 2013-09-17 12:54:03 -04:00
psychobunny
08316496cd fixes #289 recent page not responsive 2013-09-16 13:53:11 -04:00
psychobunny
d5fd985f0d applied previous commit to default theme instead 2013-09-16 13:50:14 -04:00
psychobunny
eab7839195 fixes #298 footer flash onload 2013-09-16 13:48:32 -04:00
Julian Lam
18717dd6f5 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-15 20:52:05 -04:00
Julian Lam
d249f411f8 closed #300 - issue where web socket connections were not working properly
with passworded redis instances
2013-09-15 20:51:36 -04:00
psychobunny
b85e2545d6 fixes #299 2013-09-15 16:32:30 -04:00
Julian Lam
79280b195e changed get_uid_by_userslug to be error-first 2013-09-15 10:15:23 -04:00
Julian Lam
4a18728e19 closed #297 2013-09-15 10:11:29 -04:00
Julian Lam
b7498416fa fixing redisstore sessions with passworded redis stores 2013-09-15 09:23:48 -04:00
Julian Lam
49b201db0f added heroku support for dynamic port reading via process.env 2013-09-15 09:09:44 -04:00
Julian Lam
be4d6761b1 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-15 07:58:07 -04:00
Julian Lam
c01ba1a3cb closed #296 2013-09-15 07:58:02 -04:00
Baris Soner Usakli
af59ff3209 closes #286, closes #287 2013-09-14 22:28:50 -04:00
Baris Soner Usakli
b0a37c6ac5 closes #293 2013-09-14 22:04:03 -04:00
Julian Lam
8b3fd9d1e9 fixed bug where deleted threads caused thread tools to disappear (js
error)
2013-09-14 11:44:06 -04:00
Julian Lam
95b16690e0 altering api route to use next() instead of echoing 404... 2013-09-14 11:01:28 -04:00
Julian Lam
f6b865a052 closed #292 2013-09-14 10:58:50 -04:00
Julian Lam
d1c756306a updates to allow dynamic addition of static directories provided by plugins 2013-09-13 11:10:17 -04:00
Julian Lam
ad5af8e123 updated screenshots for 0.0.6 2013-09-12 17:15:14 -04:00
psychobunny
ba89285c74 editorconfig; whitespace from favourites 2013-09-12 14:19:46 -04:00
psychobunny
43c0c2ec2a fix for favouriting 2013-09-12 14:16:13 -04:00
psychobunny
a277104ad5 dehinted favourites.js 2013-09-12 14:13:00 -04:00
psychobunny
84afffc761 categories.js jshinted 2013-09-12 14:07:29 -04:00
Julian Lam
5e5680fd13 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-12 13:55:45 -04:00
Julian Lam
727da38fb6 fixing issue where upgrade path only applies db fixes to default redis host and port 2013-09-12 13:55:38 -04:00
Baris Soner Usakli
79096cfdce online count fix for anon users 2013-09-12 13:10:14 -04:00
Baris Soner Usakli
a848f82b8f fix for upgrade script 2013-09-12 12:44:18 -04:00
Julian Lam
71bdd4608b removed defunct installation routes 2013-09-12 12:38:46 -04:00
Julian Lam
f04a948152 Merge pull request #282 from vincentbriglia/master
User search in non-admin removes search header/input
2013-09-12 09:17:33 -07:00
Vincent Briglia
15be3e52ae Merge remote-tracking branch 'upstream/master' 2013-09-12 16:05:43 +00:00
Vincent Briglia
b1003e954e user search shouldn't replace the whole top-level userbox 2013-09-12 16:04:04 +00:00
Julian Lam
f2b9a7ff0c Merge pull request #279 from vincentbriglia/master
isEmailAvailable returns inverted value
2013-09-12 06:39:53 -07:00
Vincent Briglia
19ad9ab224 inverted value required, double exclamation mark just casts as boolean 2013-09-12 13:37:03 +00:00
Vincent Briglia
8eae8a4fb5 !undefined = true
!!undefined = false
!!!undefined = true

!false = true
!!false = false
!!!false = true

!true = false
!!true = true
!!!true = false

when using isEmailAvailable (positive) in correlation with exists (positive) make sure that the outcome is a positive return (and not an inverted)
2013-09-12 13:07:00 +00:00
Baris Usakli
85fbe38e71 fixed to icon selection in admin/categories 2013-09-11 16:44:40 -04:00
Baris Usakli
e896fd1fd6 show created category right away 2013-09-11 16:20:00 -04:00
Baris Usakli
916150de01 closes #228 2013-09-11 16:04:34 -04:00
Julian Lam
732204f11b Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-11 15:41:48 -04:00
Julian Lam
1df50ff855 updated topic viewing so that markAsRead marks any relevant notifications read (issue #219) 2013-09-11 15:41:20 -04:00
psychobunny
11042858b8 Merge pull request #277 from designcreateplay/theme/cerulean
theme/cerulean
2013-09-11 12:31:00 -07:00
Baris Usakli
89ca2319f5 removed console.log 2013-09-11 15:28:20 -04:00
psychobunny
2f4fd310bd moved everything into the header class 2013-09-11 15:27:46 -04:00
Baris Usakli
6d7919ad85 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-11 15:24:15 -04:00
Baris Usakli
59467c906d misc fixes 2013-09-11 15:24:01 -04:00
psychobunny
def871ac89 fixed overlapping text in topic text-title 2013-09-11 15:21:23 -04:00
psychobunny
6fe5a93a73 header updates 2013-09-11 15:08:08 -04:00
psychobunny
7796c9269c cleaned up unread.tpl 2013-09-11 14:57:59 -04:00
psychobunny
ec150d4a4c removed unnecessary markup from users 2013-09-11 14:53:31 -04:00
psychobunny
49abff1609 moved footer style into cerulean 2013-09-11 14:49:31 -04:00
psychobunny
be59c16aad updated footer to follow standards 2013-09-11 14:47:58 -04:00
Julian Lam
f1144f3a7e Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-11 14:41:30 -04:00
Julian Lam
9ebff816dc fixing error where sometimes the admin panel js was not loaded 2013-09-11 14:41:08 -04:00
psychobunny
4c3eacd745 fixed footer markup 2013-09-11 14:37:43 -04:00
Baris Usakli
325e402d0f Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-11 13:53:51 -04:00
Baris Usakli
251587cb86 return err form updateProfile 2013-09-11 13:53:42 -04:00
Julian Lam
9fb1f8acf8 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-11 13:49:46 -04:00
Julian Lam
cffbc76da1 closed #275, refactored configs to use new method "list", which returns error first 2013-09-11 13:49:17 -04:00
Baris Usakli
be51025048 the online count on the footer uses the websockets now, it should also instantly update when people come online or go offline, #273 2013-09-11 13:24:16 -04:00
Baris Usakli
ae6f9fc87c changed notifications to use relative path 2013-09-11 13:04:04 -04:00
Baris Usakli
e61a7bff65 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-11 13:03:12 -04:00
Baris Usakli
bb14881b63 change getUserData to return err 2013-09-11 13:02:55 -04:00
Julian Lam
a7f1bfa7c9 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-11 12:55:34 -04:00
Julian Lam
7b3384a6be fixing some bad logic in app starting 2013-09-11 12:55:29 -04:00
Baris Usakli
efa8717fc5 getMultipleUserFields returns err now 2013-09-11 12:49:54 -04:00
psychobunny
9e42cee87c modules/taskbar.less, more cleanup of style 2013-09-11 12:34:56 -04:00
psychobunny
9fe30b905b modules.less, moved postWindow out. clean up unused css in style 2013-09-11 12:24:10 -04:00
Julian Lam
2fc9afa4f4 updated default category descriptions 2013-09-11 11:22:22 -04:00
psychobunny
732c8b1f7f header, footer,.. started cleaning up style (the big 'un) 2013-09-10 17:12:07 -04:00
psychobunny
19be3bc55c serious clean up of topic.less 2013-09-10 16:36:47 -04:00
Julian Lam
8a7e2577aa Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-10 16:26:38 -04:00
Julian Lam
e32494879c closed #270 2013-09-10 16:26:26 -04:00
psychobunny
ac4803961a topic cleanup and begin cerulean port 2013-09-10 15:50:21 -04:00
psychobunny
de8d6d3c8c category, removing deprecated thread-rating 2013-09-10 15:13:59 -04:00
psychobunny
392b80fe14 home.less 2013-09-10 15:10:51 -04:00
psychobunny
9fa75a8b45 init - cerulean theme 2013-09-10 15:02:03 -04:00
psychobunny
b19097ab8f moved .less files into the vanilla theme folder 2013-09-10 14:23:46 -04:00
psychobunny
31624b32e9 footer no longer overlaps composer; fixes 272 2013-09-10 13:16:10 -04:00
Julian Lam
b8b6558f53 deleting and restoring posts and topics now update the corresponding RSS feeds
fixed #257
2013-09-10 12:34:48 -04:00
Julian Lam
a53e75aeaa closed #271 2013-09-10 11:25:19 -04:00
Baris Usakli
7798004568 fixed active_users not deleting over 10 2013-09-09 16:27:16 -04:00
Baris Usakli
63873575a5 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-09 15:09:15 -04:00
Baris Usakli
9845df4d1b another fix to path in image upload 2013-09-09 15:09:05 -04:00
psychobunny
462d5f5d05 spacing between pin/lock icons in cat view 2013-09-09 14:34:39 -04:00
psychobunny
1d860923d0 booleanifying the isAdmin return... now we're admins again! :) 2013-09-09 14:31:22 -04:00
Baris Usakli
c9a3fac654 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-09 14:29:50 -04:00
Baris Usakli
a1c9685b49 fixed image upload, delete temp file if there is error 2013-09-09 14:29:40 -04:00
psychobunny
c16c4aac44 console.log 2013-09-09 14:17:11 -04:00
psychobunny
e80ff9c551 moved RSS icon away from the sharing buttons (category view); added back RSS icon to topic view. closes #259 2013-09-09 14:08:30 -04:00
psychobunny
3176ccc6e3 fixed footer flickering 2013-09-09 14:00:11 -04:00
Baris Usakli
b24196be36 closes #269 2013-09-09 13:37:50 -04:00
Baris Soner Usakli
52365a9755 upgrade user checks for undefined data 2013-09-07 19:12:49 -04:00
Julian Lam
41bea9f50c adding upgradeAdmin method to upgrade script -- pending fix to upgradeUsers 2013-09-07 19:01:17 -04:00
Julian Lam
3416c7bb3c Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-07 15:49:35 -04:00
Julian Lam
ce126b11fb fixes to administrator methods 2013-09-07 15:49:23 -04:00
Baris Soner Usakli
99db8fc3fe fixed admin/users page events and buttons 2013-09-07 15:48:38 -04:00
Julian Lam
780cec2596 missing alert-warning class in plugins admin page 2013-09-07 11:55:35 -04:00
Julian Lam
d3b4cb71c0 resolved race condition in plugin auto-enabling (not much of a race... I'd
always lose!!)
2013-09-07 11:53:55 -04:00
Julian Lam
1758c3e3f2 fix for admin templates 2013-09-07 11:51:37 -04:00
Julian Lam
995fa7d6fd interim commit -- enabling default plugins on setup 2013-09-07 11:45:04 -04:00
Julian Lam
87abe426d8 fixing 'use_port' to save into config as boolean 2013-09-07 11:29:26 -04:00
Julian Lam
bec0b46a2c refactoring installation scripts to use node prompt module, lots of other fixes
fixed #263, fixed #264, fixed #265
2013-09-07 10:40:20 -04:00
Baris Soner Usakli
a88ddc2a4d fixed image upload 2013-09-05 14:12:39 -04:00
Baris Soner Usakli
1f60578a63 closes #261 2013-09-05 12:18:51 -04:00
psychobunny
2f70489e22 outgoing links still BS2, fixed. 2013-09-05 23:55:57 +08:00
psychobunny
3b75734672 footer.less, added stats cards (ala soundsz) and replaced old footer 2013-09-05 23:28:15 +08:00
Julian Lam
ff4b35d6f1 minor bug with feed posttime if it was never edited 2013-09-04 14:16:11 -04:00
Julian Lam
e5b26fdad0 removing node-rss dep 2013-09-04 14:08:24 -04:00
Julian Lam
bcbcf40eae Merge branch 'feed_refactor' 2013-09-04 13:54:44 -04:00
Julian Lam
661fdfb43e bugfixing and allowing feeds to be generated on request (as opposed to just updated on posting 2013-09-04 13:52:28 -04:00
Julian Lam
31f08c49e7 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-04 13:36:33 -04:00
Julian Lam
2999d61ac9 minor fix... turning plugin libraries array into object 2013-09-04 13:36:25 -04:00
Baris Usakli
ca72a4b0fc fixed comma to semicolon 2013-09-04 12:13:36 -04:00
Julian Lam
164977972e totally derped commit 2013-09-03 23:42:26 -04:00
Julian Lam
b6fbfcc814 interim commit for new feed refactor 2013-09-03 23:18:25 -04:00
Julian Lam
418700ce3f changing info to warn when config file not found 2013-09-03 22:21:56 -04:00
Julian Lam
1d52557562 removing hook ordering message from non-development runs 2013-09-03 22:03:04 -04:00
psychobunny
45e24c54ce pagination scroll block fix + cleanup 2013-09-04 06:01:23 +08:00
psychobunny
abd909d23b pagination - scroll to top / bottom arrows 2013-09-04 04:50:51 +08:00
Julian Lam
f39932ece7 group join leave, and updating 2013-09-03 16:05:01 -04:00
Julian Lam
03fb649274 Merge branch 'master' into user_groups 2013-09-03 14:09:18 -04:00
Julian Lam
54e9e95076 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-03 13:20:49 -04:00
Julian Lam
201fb4d73e fixed #256 - where saving and retrieval of RSS feeds wasn't working on some instances due to a relative path being passed to read/writeFile 2013-09-03 13:20:19 -04:00
Baris Usakli
e0cc35ba66 creating topics shows up in user profile too issue #220 2013-09-03 13:19:51 -04:00
Baris Usakli
d3818e888e Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-03 13:11:44 -04:00
Baris Usakli
a2af4a3e91 closes #220 2013-09-03 13:11:34 -04:00
Julian Lam
02a02fa64c updated getTopicWithPosts to use a start and end option, and fixed issue with RSS feed saving (issue #256) 2013-09-03 13:11:21 -04:00
Julian Lam
9fae0d2505 removing dupe main post from posts array in topic noscript 2013-09-03 12:36:48 -04:00
Julian Lam
e23198bbcc Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-03 12:21:53 -04:00
Julian Lam
0a8c43901d more fixes to noscript 2013-09-03 12:21:48 -04:00
Baris Usakli
e9054301d1 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-09-03 12:18:38 -04:00
Baris Usakli
caaede7e84 added loading spinner to topic.tpl 2013-09-03 12:18:26 -04:00
Julian Lam
3ed9e1dd51 closed #253, closed #252 2013-09-03 12:17:55 -04:00
Julian Lam
207eccc505 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-03 11:48:39 -04:00
Julian Lam
45d5524df7 fixing minor errors in css in composer 2013-09-03 11:48:34 -04:00
Baris Usakli
1f08e407d1 closes #248 2013-09-03 11:45:15 -04:00
Baris Usakli
2c215afe33 closes #249 2013-09-03 11:29:56 -04:00
Julian Lam
d1d2b03dfd Merge branch 'master' into user_groups 2013-09-03 11:06:22 -04:00
Julian Lam
c1a41c6605 added hook priority to plugin hook registration 2013-09-02 21:53:15 -04:00
Julian Lam
d1a17b39ea allowing npm update of socket.io to newer version 2013-09-02 17:14:56 -04:00
Julian Lam
bf365bedfd pushing fix to issue where htmlfile transport for socket.io was causing
NodeBB to not work at all
2013-09-02 17:00:33 -04:00
Julian Lam
6a1ab47a08 hardcoding the exclusion of 'htmlfile' from web sockets, since it seems to
cause NodeBB to fail
2013-09-02 16:53:15 -04:00
Julian Lam
5fdaa6b0ee Merge branch 'master' into user_groups 2013-09-01 23:19:29 -04:00
Julian Lam
a6b0c2638a user list on group edit modal 2013-09-01 23:19:16 -04:00
Julian Lam
a671d08f0b updated readme - nginx not required 2013-09-01 19:17:40 -04:00
Julian Lam
5e869a5e5c Merge branch 'master' into user_groups 2013-09-01 16:14:24 -04:00
Julian Lam
0e6109ff2b a better conditional, just to please baris 2013-09-01 16:01:15 -04:00
Julian Lam
0bff6ee504 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-09-01 15:27:51 -04:00
Julian Lam
7cbe429be9 fixing issue with plugins trying to load 0 plugins and crashing 2013-09-01 15:27:45 -04:00
Baris Soner Usakli
09b578522f added prefic to less middleware 2013-09-01 15:21:28 -04:00
psychobunny
40897d0be6 themed reset.tpl and reset_code.tpl to BS3, closes #239 2013-08-31 03:46:48 +08:00
Baris Usakli
f49bc088fc Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-30 15:08:12 -04:00
Baris Usakli
7795a9ead2 fixed a crash 2013-08-30 15:07:58 -04:00
psychobunny
2c238f49e0 added search button, hiding the form by default. also fixes issue #241
notes: mobile needs a dedicated notification screen, imo, rather than
using the same popup. and /search needs a form on the page itself.
2013-08-31 02:59:50 +08:00
Baris Usakli
599789634a closes #227 2013-08-30 14:47:31 -04:00
Baris Usakli
77359f7b83 added space between user name and colon 2013-08-30 14:27:54 -04:00
Baris Usakli
7cff55a160 closes #209 2013-08-30 14:25:59 -04:00
Julian Lam
8d26eb4e07 minor stuff and such 2013-08-30 13:32:44 -04:00
Baris Usakli
ad5e3ebce3 closes #236 2013-08-30 12:36:06 -04:00
Baris Usakli
2deb0e1708 closes #238 2013-08-30 12:04:35 -04:00
Baris Usakli
b2f383f095 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-30 11:47:37 -04:00
Baris Usakli
22ababd87a closes #240 2013-08-30 11:47:26 -04:00
Julian Lam
167c1fa348 fixing topic CSS so the h3 styles only apply to the topic title 2013-08-30 11:43:46 -04:00
Baris Usakli
3b1bf67436 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-30 11:40:32 -04:00
Baris Usakli
d2eb73df96 can add and remove images from posts 2013-08-30 11:40:20 -04:00
Julian Lam
4d075efcd3 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-30 11:19:06 -04:00
Julian Lam
8a53c56a46 updated install script to only populate "socket" object in client config with "address", which takes "use_port" into account.
This closes #234
2013-08-30 11:18:15 -04:00
Julian Lam
73d4152d1d updated readme to add nginx version requirement 2013-08-30 11:07:44 -04:00
Julian Lam
c0a90bd677 interim commit 2013-08-30 07:43:47 -04:00
Baris Soner Usakli
106157a951 removed commented out line 2013-08-29 21:19:35 -04:00
Baris Soner Usakli
ab9cf6d036 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-29 21:18:34 -04:00
Baris Soner Usakli
54ba6efc93 can edit and add an image to a post, still cant remove previously added images #issue #233 2013-08-29 21:18:23 -04:00
Julian Lam
85ac4aac94 group creation and deletion 2013-08-29 14:55:30 -04:00
Baris Usakli
691b6611d0 user search uses reds now 2013-08-29 13:40:04 -04:00
Baris Usakli
1b33b4425b removed active from pill, #issue 231 2013-08-29 11:49:53 -04:00
Baris Usakli
41defbcf9c added delays to home page animations 2013-08-29 11:41:27 -04:00
Baris Soner Usakli
928594fc7c Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-28 22:08:54 -04:00
Baris Soner Usakli
cc1f668308 changed chat online offline messages 2013-08-28 22:08:46 -04:00
Julian Lam
f2b8813fb9 brain dumped groups lib 2013-08-28 22:06:55 -04:00
Julian Lam
5ed7c31278 removing console logs 2013-08-28 21:35:00 -04:00
Julian Lam
13b456bffd fixing up bs3 integration for themes page 2013-08-28 21:33:17 -04:00
Julian Lam
938dee481b Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-28 21:30:22 -04:00
Baris Soner Usakli
a69140faa9 show error message on failed login 2013-08-28 17:22:48 -04:00
psychobunny
6a5ebdb1ef fixed edit/delete/favourite buttons not working on subposts / topics 2013-08-29 05:06:22 +08:00
Julian Lam
ece4d083a5 Merge branch 'master' of github.com:designcreateplay/NodeBB 2013-08-28 16:33:09 -04:00
Julian Lam
96688a8ffe changing method to retrieve themes to use /node_modules 2013-08-28 16:32:59 -04:00
Baris Soner Usakli
e9b0aa537f center speech arrow 2013-08-28 16:32:45 -04:00
psychobunny
4052c3e817 Merge pull request #226 from designcreateplay/bootstrap3
Bootstrap3
2013-08-28 13:20:23 -07:00
psychobunny
4a6e838b18 updated new settings markup to bs3 2013-08-29 04:15:45 +08:00
psychobunny
3773f6b44f merge conflicts + updated search.tpl to BS3 2013-08-29 04:12:36 +08:00
psychobunny
a19e836d63 fixed taskbar, tabs were stacking vertically instead of horizontally 2013-08-29 04:03:36 +08:00
psychobunny
aa5e5f9cc0 fixed bootbox3 issue in categories 2013-08-29 04:00:43 +08:00
psychobunny
6647e75c77 updated bootbox to BS3 2013-08-29 03:45:15 +08:00
psychobunny
f85a514ee3 fixed a bug in ACP where you could accidentally delete or ban a user even if you clicked on cancel. 2013-08-29 03:43:14 +08:00
psychobunny
619dd84d81 help block text in picture uploader 2013-08-29 03:26:00 +08:00
psychobunny
f9fdfab19e accountedit.tpl -> modal updates for picture selector and uploader 2013-08-29 03:23:45 +08:00
psychobunny
ca46d0f8e1 fixed disconnect modal; fixed modal overlay BS3 issue 2013-08-29 03:14:43 +08:00
psychobunny
bb8f75b4be merged.. conflicted up the ass. registration looks gud again 2013-08-29 03:01:58 +08:00
Baris Soner Usakli
0db599a478 closes #205 2013-08-28 14:46:18 -04:00
Baris Soner Usakli
902e60fab2 added password settings to admin 2013-08-28 13:53:20 -04:00
Baris Soner Usakli
87f48e2cc9 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-28 13:48:05 -04:00
Baris Soner Usakli
cc55073107 issue #224 2013-08-28 13:47:52 -04:00
Julian Lam
96c43b4607 fixed #221 - issue where replies would not automatically refresh a post after editing 2013-08-28 12:29:15 -04:00
Julian Lam
f1b4367168 closed #180, closed #223 2013-08-28 11:00:34 -04:00
Julian Lam
4416f8530d matching connect-redis session with cookie ttl 2013-08-28 10:18:17 -04:00
Baris Soner Usakli
4e48ab2363 changed max age to milliseconds 2013-08-27 23:40:38 -04:00
psychobunny
b5a26696f8 fixed the chat modal 2013-08-28 04:36:14 +08:00
psychobunny
4307229ae0 added profile link and chat button fn to both user dropdowns 2013-08-28 04:09:53 +08:00
psychobunny
b681f01be9 fixed profile link in topic user dropdown menu 2013-08-28 03:56:34 +08:00
Baris Usakli
c262027728 Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-27 15:55:48 -04:00
Baris Usakli
6a08fedf18 closes #210 2013-08-27 15:55:44 -04:00
psychobunny
e471b4edf5 category + recent replies layout rejigged 2013-08-28 03:43:05 +08:00
psychobunny
dd86812822 some BS2 buttons managed to escape my grasp.. until now. 2013-08-28 03:11:08 +08:00
psychobunny
ac311ab75d fixed derpy profile image size in /search 2013-08-28 03:08:05 +08:00
psychobunny
c9387313e2 ACP header and all admin pages BS3'd 2013-08-28 03:04:57 +08:00
Julian Lam
97641dbcda fixing teasers, that I broke in the last commit 2013-08-27 15:00:35 -04:00
Julian Lam
54fdbcd947 fixing broken category 2013-08-27 14:57:21 -04:00
psychobunny
7636e09284 admin hero-unit to jumbotron and updated welcome message to be slightly less embarassing 2013-08-28 02:35:07 +08:00
psychobunny
7666ed3010 fixed two bugs in admin, meta.buildTitle errors out because no notification icon is present; pagination element missing
& final less cleanup for today
2013-08-28 02:32:38 +08:00
psychobunny
e82a9ec37a more less cleanup 2013-08-28 02:25:17 +08:00
psychobunny
770d9e5b7c removed mobile menu markup + css, more cleanup 2013-08-28 02:19:59 +08:00
Julian Lam
5f0e0c993e adding localhost as default for email server 2013-08-27 14:16:56 -04:00
psychobunny
7be69b0b54 more cleanup, account.less, removed mobile sidebar functionality completely 2013-08-28 02:11:13 +08:00
psychobunny
251beb7f9b updated less to use .pointer mixin 2013-08-28 02:05:50 +08:00
Baris Usakli
1c27cbd90a Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-27 14:04:25 -04:00
Baris Usakli
4b4b26651a derp 2013-08-27 14:04:22 -04:00
Julian Lam
1a6ba8c230 fixed derpiness I introduced last commit 2013-08-27 14:04:09 -04:00
psychobunny
06904506d4 header.less, got rid of some unused css classes related to the original post window 2013-08-28 02:02:35 +08:00
psychobunny
62a28825f8 more css cleanup 2013-08-28 02:00:10 +08:00
psychobunny
c6c693acd2 some cleanup of topics.less 2013-08-28 01:58:56 +08:00
Julian Lam
8d04454457 closed #218 2013-08-27 13:52:30 -04:00
Baris Usakli
aeb831eeff closes #217 2013-08-27 13:47:19 -04:00
psychobunny
67db78ad66 styled topic's user dropdown 2013-08-28 01:44:41 +08:00
psychobunny
06809ab5fc got rid of a lot of junk markup and unused css classes; standardized the pulling of pid/uid across post.js code 2013-08-28 01:41:32 +08:00
Baris Usakli
08ba911738 closes #216 2013-08-27 13:38:09 -04:00
Baris Usakli
59f4b6788a Merge branch 'master' of https://github.com/designcreateplay/NodeBB 2013-08-27 13:32:54 -04:00
Baris Usakli
a317a4d689 closes #206 2013-08-27 13:32:43 -04:00
psychobunny
6e164e61aa removed some unnecessary CSS 2013-08-28 01:30:43 +08:00
psychobunny
2098bf84a1 finished pagination for topic view 2013-08-28 01:23:19 +08:00
Julian Lam
860a83ba90 closed #212 2013-08-27 13:04:18 -04:00
Julian Lam
abce5fd120 refactored postTools.toHTML to fire post.parse hook, and removed auto-markdowning
closed #214
2013-08-27 12:50:15 -04:00
Julian Lam
0243e9c5be issue #214, preparing for addition of post.parse hook by renaming markdownToHTML to just "toHTML", and making it asynchronous. 2013-08-27 12:14:27 -04:00
Baris Usakli
8a4a0154f6 closes #211, closes #213 2013-08-27 12:10:23 -04:00
psychobunny
469a5221ed added postcount to topics return; started scrollspy behaviour for "postid / postcount" in topics. 2013-08-27 06:36:49 +08:00
psychobunny
f251b9c6c5 Merge branch 'master' into bootstrap3 2013-08-27 06:03:05 +08:00
Baris Usakli
c4228be86c bold active section in account page 2013-08-26 17:50:31 -04:00
psychobunny
94e565aa00 account pages done; 403; 404 2013-08-27 05:42:09 +08:00
psychobunny
cc6e028b1d fixed alert-notify (and blanks) to alert-warning 2013-08-27 05:16:49 +08:00
psychobunny
33a69abece mark all as read button was unstyled 2013-08-27 05:11:39 +08:00
psychobunny
6a4947533f fixed BS3 issue, navbar now centered; fixed topic view sub-post moderator tools layout 2013-08-27 05:07:52 +08:00
psychobunny
879a31f51e had to remove hidden-xs from the footer - BS3's !important takes precedence over the fading animation and looks ugly (flex) 2013-08-27 04:51:29 +08:00
psychobunny
49fc87e295 removed unnecessary css definitions (previous fixes for BS2), and fixed category width on mobile screens 2013-08-27 04:42:04 +08:00
psychobunny
fe89f1f096 added a .hidden-mobile class for inline block elements. also removed button text on jumbotron for mobile devices 2013-08-27 04:37:16 +08:00
psychobunny
346681ba27 removed mobile menu until it can be pluginified. refactored topics UX a hell of a lot, looks much better on both desktop+mobile now. 2013-08-27 04:30:00 +08:00
psychobunny
13e13cd5a8 some changes to topic view + BS3 upgrade 2013-08-24 04:34:13 +08:00
psychobunny
0f8aad52b2 updated all alert-error to alert-danger (wtf did BS3 need to change that?) 2013-08-24 03:36:44 +08:00
psychobunny
b6a5cbf956 trim whitespace mode on 2013-08-24 03:30:34 +08:00
psychobunny
a482d4db5f fixed /recent, /unread 2013-08-24 03:10:15 +08:00
psychobunny
df15dceaef merging master to pull baris' latest fixes 2013-08-24 01:59:35 +08:00
psychobunny
9f2e192993 jumbotron updates. I'll come back to fixing the button layout on mobile later 2013-08-24 01:27:12 +08:00
psychobunny
d62f36c6a0 now /users looks as it should. made some tweaks to user search to improve UX as well 2013-08-24 01:11:50 +08:00
psychobunny
2427724868 navbar changes + logout screen + replaced hardcoded sitename on logout screen with breadcrumbs
note: perhaps breadcrumbs should be dynamically created in future
instead of copypasted everywhere.
2013-08-24 00:50:16 +08:00
psychobunny
9b35d8f8e1 migrating the rest of row-fluids to row 2013-08-24 00:44:27 +08:00
psychobunny
a859f4524c container-fluid to container 2013-08-24 00:43:38 +08:00
psychobunny
8be896aebb label-important to label-danger 2013-08-24 00:43:03 +08:00
psychobunny
a008cf971d register and login -> BS3 2013-08-24 00:38:40 +08:00
psychobunny
316077fffb category view starting to look like it should 2013-08-22 05:04:58 +08:00
psychobunny
4373037071 fixed composer window having 0 width; recent replies looks gud again 2013-08-22 04:05:48 +08:00
psychobunny
24e81c873c small user img-thumbnails changed to img-rounded 2013-08-22 03:55:31 +08:00
psychobunny
9e4a3905a0 moved most span12 and span9 over to BS3; img-polaroid to img-thumbnail 2013-08-22 03:39:43 +08:00
psychobunny
b54b4a6580 breadcrumbs don't need a container element anymore 2013-08-22 03:28:27 +08:00
psychobunny
f33a9c3941 updated hidden/visible device flags to BS3 2013-08-22 03:25:17 +08:00
psychobunny
d792798963 breadcrumbs 2013-08-22 03:22:33 +08:00
psychobunny
e5af4f6299 BS3 source files, damn you windows github 2013-08-22 03:18:03 +08:00
psychobunny
ef5bd9dc61 moved home categories into its own less file 2013-08-22 03:17:52 +08:00
psychobunny
8834feac65 BS3 sourcefiles + modified category row to new format. now mobile has two columns of categories, looks much neater. 2013-08-22 03:13:49 +08:00
psychobunny
2f70dd732d BS3 basic integration; btn-large to btn-lg (inc FA); navbar fixed 2013-08-22 02:57:18 +08:00
204 changed files with 32886 additions and 17138 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
root = true
[*.js, *.css, *.tpl]
indent_style = tab

8
.gitignore vendored
View File

@@ -1,4 +1,4 @@
#################
#################
## npm
#################
@@ -6,10 +6,14 @@ npm-debug.log
node_modules/
sftp-config.json
config.json
public/src/nodebb.min.js
public/config.json
public/css/*.css
public/themes/*
!/public/themes/vanilla
!/public/themes/cerulean
!/public/themes/modern
*.sublime-project
*.sublime-workspace
plugins/*
.project
.project

17
.jsbeautifyrc Normal file
View File

@@ -0,0 +1,17 @@
{
"indent_size": 4,
"indent_char": " ",
"indent_level": 0,
"indent_with_tabs": true,
"preserve_newlines": true,
"max_preserve_newlines": 10,
"jslint_happy": true,
"brace_style": "collapse",
"keep_array_indentation": false,
"keep_function_indentation": false,
"space_before_conditional": true,
"break_chained_methods": false,
"eval_code": false,
"unescape_strings": false,
"wrap_line_length": 0
}

86
.jshintrc Normal file
View File

@@ -0,0 +1,86 @@
{
// JSHint Default Configuration File (as on JSHint website)
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 4, // {int} Number of spaces to use for indentation
"latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : false, // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : false, // true: Require all defined variables be used TODO: Set this to true, update codebase.
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"trailing" : false, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements"
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : true, // jQuery
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Legacy
"nomen" : false, // true: Prohibit dangling `_` in variables
"onevar" : false, // true: Allow only one `var` statement per function
"passfail" : false, // true: Stop on first error
"white" : false, // true: Check against strict whitespace and indentation rules
// Custom Globals
"globals" : {} // additional predefined global variables
}

View File

@@ -1,12 +1,15 @@
Please support NodeBB development! Check out our IndieGoGo campaign and like, share, and follow us :)
[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://imgur.com/BdImzGs.png)
* [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")
* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
![NodeBB Topic Page](http://imgur.com/Lk7E3KR.png)
![NodeBB Main Category Listing](http://i.imgur.com/ZBrHqLr.png)
![NodeBB Topic Page](http://i.imgur.com/YSBA6Vr.png)
## How can I follow along/contribute?
@@ -16,7 +19,11 @@ Please support NodeBB development! Check out our IndieGoGo campaign and like, sh
## Requirements
NodeBB requires a version of Node.js at least 0.8 or greater, and a Redis version 2.6 or greater.
NodeBB requires the following software to be installed:
* A version of Node.js at least 0.8 or greater
* Redis, version 2.6 or greater.
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
## Installation
@@ -54,3 +61,7 @@ NodeBB can also be started with helper programs, such as `supervisor` and `forev
*(Optional)* Some server configurations may install the node binary as `nodejs` instead of `node`. You can re-map it (so as to not break compatibility with `node-supervisor`) by running the following command:
# update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10
## Upgrading NodeBB
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://github.com/designcreateplay/NodeBB/wiki/Upgrading-NodeBB)

208
app.js
View File

@@ -16,80 +16,86 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Configuration setup
nconf = require('nconf');
nconf.argv().file({ file: __dirname + '/config.json'});
(function () {
"use strict";
var fs = require('fs'),
winston = require('winston'),
pkg = require('./package.json'),
url = require('url'),
meta = require('./src/meta.js');
// Configuration setup
var nconf = require('nconf');
nconf.argv().env();
// Runtime environment
global.env = process.env.NODE_ENV || 'production',
var fs = require('fs'),
async = require('async'),
winston = require('winston'),
pkg = require('./package.json'),
path = require('path'),
meta;
// Runtime environment
global.env = process.env.NODE_ENV || 'production';
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
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('upgrade')) {
meta.configs.init(function() {
require('./src/upgrade').upgrade();
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
colorize: true
});
} 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/');
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.');
winston.add(winston.transports.File, {
filename: 'error.log',
level: 'error'
});
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);
// 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
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 (fs.existsSync(__dirname + '/config.json') && (!nconf.get('setup') && !nconf.get('upgrade'))) {
// Load server-side configs
nconf.file({
file: __dirname + '/config.json'
});
meta = require('./src/meta.js');
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/');
winston.info('Initializing NodeBB v' + pkg.version + ', on port ' + nconf.get('port') + ', using Redis store at ' + nconf.get('redis:host') + ':' + nconf.get('redis:port') + '.');
winston.info('NodeBB instance bound to: ' + (nconf.get('bind_address') || 'Any address'));
if (process.env.NODE_ENV === 'development') {
winston.info('Base Configuration OK.');
}
var categories = require('./src/categories.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')
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);
};
DEVELOPMENT = true;
var templates = require('./public/src/templates.js'),
translator = require('./public/src/translator.js'),
webserver = require('./src/webserver.js'),
SocketIO = require('socket.io').listen(global.server, { log: false, transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket']}),
websockets = require('./src/websockets.js'),
plugins = require('./src/plugins'); // Don't remove this - plugins initializes itself
global.configuration = {};
global.templates = {};
websockets.init(SocketIO);
(function(config) {
config['ROOT_DIRECTORY'] = __dirname;
global.templates = {};
global.translator = translator;
translator.loadServer();
// todo: replace below with read directory code, derp.
templates.init([
'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index',
'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext',
@@ -99,62 +105,36 @@ if(nconf.get('upgrade')) {
]);
templates.ready(webserver.init);
});
//setup scripts to be moved outside of the app in future.
function setup_categories() {
winston.info('Checking categories...');
categories.getAllCategories(function(data) {
if (data.categories.length === 0) {
winston.info('Setting up default categories...');
} else if (nconf.get('upgrade')) {
meta = require('./src/meta.js');
fs.readFile(config.ROOT_DIRECTORY + '/install/data/categories.json', function(err, default_categories) {
default_categories = JSON.parse(default_categories);
for (var category in default_categories) {
admin.categories.create(default_categories[category]);
}
});
winston.info('Hardcoding uid 1 as an admin');
var user = require('./src/user.js');
user.makeAdministrator(1);
} else {
winston.info('Categories OK. Found ' + data.categories.length + ' categories.');
}
});
}
setup_categories();
}(global.configuration));
});
} else {
// New install, ask setup questions
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');
process.stdout.write(
"\nWelcome to NodeBB!\nThis looks like a new installation, so you'll have to answer a " +
"few questions about your environment before we can proceed.\n\n" +
"Press enter to accept the default setting (shown in brackets).\n\n\n"
);
install.setup(function(err) {
if (err) {
winston.error('There was a problem completing NodeBB setup: ', err.message);
meta.configs.init(function () {
require('./src/upgrade').upgrade();
});
} else {
// New install, ask setup questions
if (nconf.get('setup')) {
winston.info('NodeBB Setup Triggered via Command Line');
} else {
if (!nconf.get('setup')) {
process.stdout.write(
"Please start NodeBB again and register a new user. This user will automatically become an administrator.\n\n"
);
}
winston.warn('Configuration not found, starting NodeBB setup');
}
process.exit();
});
}
var install = require('./src/install');
winston.info('Welcome to NodeBB!');
winston.info('This looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.');
winston.info('Press enter to accept the default setting (shown in brackets).');
install.setup(function (err) {
if (err) {
winston.error('There was a problem completing NodeBB setup: ', err.message);
} else {
winston.info('NodeBB Setup Completed.');
}
process.exit();
});
}
}());

View File

@@ -1,7 +1,7 @@
[
{
"name": "Announcements",
"description": "A place to talk about whateeeever you want",
"description": "Announcements regarding our community",
"blockclass": "category-blue",
"icon" : "icon-bullhorn"
},
@@ -13,61 +13,61 @@
},
{
"name": "NodeBB Development",
"description": "Bugs? Dont worry, we dont read this thread, so post them here.",
"description": "NodeBB development news and announcements",
"blockclass": "category-blue",
"icon" : "icon-github"
},
{
"name": "Blogs",
"description": "In future an example of how a hidden category should look like.",
"description": "Blog posts from individual members",
"blockclass": "category-blue",
"icon" : "icon-pencil"
},
{
"name": "Feature Requests",
"description": "In future an example of how a hidden category should look like.",
"description": "Got a feature request you'd like to see? Give us a shout here.",
"blockclass": "category-purple",
"icon" : "icon-lightbulb"
},
{
"name": "Bug Reports",
"description": "In future an example of how a hidden category should look like.",
"description": "Having trouble with NodeBB? Let us know...",
"blockclass": "category-purple",
"icon" : "icon-cogs"
},
{
"name": "NodeBB Addons",
"description": "In future an example of how a hidden category should look like.",
"name": "NodeBB Plugins",
"description": "Enhance your NodeBB with plugins!",
"blockclass": "category-purple",
"icon" : "icon-plus-sign"
},
{
"name": "NodeBB Link Exchange",
"description": "In future an example of how a hidden category should look like.",
"description": "Link exchange",
"blockclass": "category-purple",
"icon" : "icon-exchange"
},
{
"name": "News",
"description": "In future an example of how a hidden category should look like.",
"description": "News from around the world",
"blockclass": "category-darkblue",
"icon" : "icon-globe"
},
{
"name": "Movies",
"description": "In future an example of how a hidden category should look like.",
"description": "Discuss the latest movies here",
"blockclass": "category-darkblue",
"icon" : "icon-film"
},
{
"name": "Games",
"description": "In future an example of how a hidden category should look like.",
"description": "Discuss the latest games here",
"blockclass": "category-darkblue",
"icon" : "icon-screenshot"
},
{
"name": "Random",
"description": "In future an example of how a hidden category should look like.",
"description": "Anything and (almost) everything welcome!",
"blockclass": "category-darkblue",
"icon" : "icon-beer"
}

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPLv3 or later",
"description": "NodeBB Forum",
"version": "0.0.6",
"version": "0.0.7",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -10,10 +10,10 @@
},
"main": "app.js",
"dependencies": {
"socket.io": "0.9.14",
"socket.io": "~0.9.16",
"redis": "0.8.3",
"express": "3.2.0",
"express-namespace": "0.1.1",
"express-namespace": "~0.1.1",
"emailjs": "0.3.4",
"cookie": "0.0.6",
"connect-redis": "1.4.5",
@@ -27,7 +27,6 @@
"bcrypt": "0.7.5",
"async": "0.2.8",
"node-imagemagick": "0.1.8",
"node-rss": "1.0.1",
"gravatar": "1.0.6",
"nconf": "~0.6.7",
"sitemap": "~0.6.0",
@@ -35,7 +34,15 @@
"request": "~2.25.0",
"reds": "~0.2.4",
"winston": "~0.7.2",
"nodebb-plugin-mentions": "~0.1.0"
"nodebb-plugin-mentions": "~0.1.0",
"nodebb-plugin-markdown": "~0.1.0",
"rss": "~0.2.0",
"prompt": "~0.2.11",
"uglify-js": "~2.4.0",
"validator": "~1.5.1"
},
"optionalDependencies": {
"hiredis": "~0.1.15"
},
"bugs": {
"url": "https://github.com/designcreateplay/NodeBB/issues"

View File

@@ -1,5 +1 @@
@import "style";
@import "topic";
@import "category";
@import "noscript";
@import "../themes/cerulean/cerulean.less";

View File

@@ -1,885 +0,0 @@
@import "mixins";
html {
overflow-y: scroll;
}
body {
/*background: #fdfdfd;*/ // port to default theme when it is implemented.
-webkit-transition: margin-bottom 250ms ease;
-moz-transition: margin-bottom 250ms ease;
-ms-transition: margin-bottom 250ms ease;
-o-transition: margin-bottom 250ms ease;
transition: margin-bottom 250ms ease;
&.composing {
margin-bottom: 350px;
}
@media (min-width: 979px)
{
padding-top: 70px;
}
@media (max-width: 979px)
{
padding-bottom: 50px;
}
}
button, a {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.none {
display: none !important;
}
.block, .show {
display: block;
}
.badge {
vertical-align: 17%;
}
.nav .badge {
vertical-align: 2%;
}
#alert_window {
position: fixed;
right: 20px;
top: 60px;
width: 300px;
height: 0px;
}
.toaster-alert {
cursor: pointer;
}
footer.footer {
color: #555;
text-align: center;
a {
color: #222;
}
}
#post_window {
width: 100%;
position: fixed;
height: 350px;
left: 0px;
bottom: 0px;
background: white;
z-index: 1500;
input {
width: 100%;
height: 30px;
padding: 5px;
}
textarea {
width: 100%;
background: #222;
height: 220px;
resize: none;
border-radius: 0;
border: 1px solid #111;
font-size: 16px;
color: #bebebe;
outline: 0;
&:focus {
outline: 0;
border:none !important;
box-shadow:none !important;
}
}
.post-title-container {
opacity: 0.8;
height: 50px;
}
.post-content-container {
opacity: 0.8;
background: #000;
width: 100%;
height: 300px;
}
}
#user_label { //belongs in header.less
img {
border: 1px solid #454;
margin-right: 8px;
margin-top: -2px;
float: left;
width:24px;
height:24px;
}
span {
font-size: 14px;
font-weight: 400;
color: #ded;
}
}
#reply_title {
font-size: 17px;
padding-top: 14px;
font-weight: 600;
}
.alt-logins {
margin: 0 0 0 1em;
padding: 0;
li {
vertical-align: top;
background: transparent;
display: none;
margin: 0.25em;
.pointer;
&.active {
.inline-block;
}
i {
-webkit-transition: color 100ms linear;
-moz-transition: color 100ms linear;
-ms-transition: color 100ms linear;
-o-transition: color 100ms linear;
transition: color 100ms linear;
&.icon-twitter-sign:hover {
color: #4099FF;
}
&.icon-facebook-sign:hover {
color: #3b5999;
}
&.icon-google-plus-sign:hover {
color: #d34836;
}
}
}
}
#thread_active_users {
float: right;
color: rgb(153,153,153);
strong {
color: rgb(100,100,100);
font-weight: 600;
cursor: pointer;
}
}
.account-username-box{
border-bottom:1px solid #e3e3e3;
margin-bottom:10px;
}
.account-sub-links a{
margin-left:10px;
}
.account-username{
font-size:20px;
font-weight:bold;
}
.account-block {
div {
padding-bottom:10px;
}
}
.account-picture-block{
display:inline-block;
vertical-align:top;
}
.account-online-status {
.icon-circle-blank {
color:red;
}
.icon-circle {
color:green;
}
}
.user-profile-picture {
width:128px;
}
.user-picture-label {
font-size:20px;
}
.account-bio-block{
display:inline-block;
vertical-align:top;
}
.account-bio-label{
display:inline-block;
width:100px;
}
.user-recent-posts {
div {
color: #333;
margin-bottom: 10px;
cursor: pointer;
overflow:hidden;
p {
color: #333;
}
}
span {
padding-top: 10px;
}
}
.category-icon {
width: 100%;
height: 90px;
text-align: center;
border-radius: 0px;
margin: 0;
padding-top:25px;
cursor: pointer;
margin-bottom: 20px;
border-radius: 5px;
overflow:hidden;
}
.category-row h4 {
font-weight: 700;
text-align: left;
/*color: #555;*/ // NOTE: color for cat/topic header links should be grey in the default theme when we get around to it.
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-row a {
text-decoration: none;
border: 0;
}
.category-purple {
@color: #ab1290;
background: @color;
color: white;
&:hover {
background: lighten(@color, 10%);
}
}
.category-darkblue {
@color: #004C66;
background: @color;
color: white;
&:hover {
background: lighten(@color, 10%);
}
}
.category-blue {
@color: #0059B2;
background: @color;
color: white;
&:hover {
background: lighten(@color, 10%);
}
}
.category-darkgreen {
@color: #004000;
background: @color;
color: white;
&:hover {
background: lighten(@color, 10%);
}
}
.category-orange {
@color: #FF7A4D;
color: white;
background: @color;
&:hover {
background: lighten(@color, 10%);
}
}
.category-list {
li {
.inline-block;
.pointer;
padding: 0.5em 0;
text-align: center;
margin: 0.5em;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
padding: 0.5em;
}
}
.hero-unit {
background: #56BCDA;
color: white;
padding: 30px;
word-wrap: break-word;
}
.users-box{
display: inline-block;
margin-top: 20px;
text-align: center;
vertical-align: top;
max-width: 104px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
img {
width:80px;
height:80px;
}
a {
margin:5px;
}
}
a:hover, .btn-link:hover, .btn-link:active, .btn-link:focus {
text-decoration:none;
}
.formatting-bar {
.no-select;
span:focus {
outline: none;
}
}
.breadcrumb {
li {
max-width: 35%;
white-space: nowrap;
text-overflow:ellipsis;
overflow: hidden;
}
}
body .navbar .nodebb-inline-block {
display:inline-block;
}
#right-menu{
float:right;
}
#admin-redis-info {
span {
display:inline-block;
width:220px;
}
}
.post-signature {
color: #666;
font-size: 12px;
border-top: 1px solid #ddd;
display: inline-block;
img {
max-width:200px;
max-height:60px;
}
}
.username-field {
.icon-circle {
font-size: 12px;
color: green;
margin-right:3px;
}
.icon-circle-blank {
font-size: 12px;
color: red;
margin-right:3px;
}
}
#chat-content {
width:95%;
height:200px;
resize:none;
}
#chat-message-input {
width:95%;
}
#content{
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;
}
}
}
#notif-list {
li {
font-size: 12px;
width: 300px;
text-align: left;
a {
white-space: normal;
}
&.unread {
background: #eceff5;
}
}
}
.taskbar {
display: none;
-moz-opacity: 0.5;
opacity: 0.5;
margin-top: 0;
-webkit-transition: opacity 250ms ease-in;
-moz-transition: opacity 250ms ease-in;
-ms-transition: opacity 250ms ease-in;
-o-transition: opacity 250ms ease-in;
transition: opacity 250ms ease-in;
&[data-active="1"] {
display: block;
}
&:hover {
-moz-opacity: 1;
opacity: 1;
}
li {
a > span {
.inline-block;
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
img {
max-width: 24px;
max-height: 24px;
margin-right: 1em;
}
&.pulse {
-webkit-animation: pulsate 2500ms linear;
-webkit-animation-iteration-count: infinite;
}
@-webkit-keyframes pulsate {
0% { background: none; }
50% { background: #e5e5e5; }
100% { background: none; }
}
}
}
.post-window {
position: fixed;
display: none;
height: 350px;
visibility: hidden;
> div {
position: absolute;
height: 100%;
background: rgba(64, 64, 64, 0.6);
visibility: visible;
.btn-toolbar {
&.formatting-bar {
width: 90%;
margin: 0 auto 8px auto;
span {
color: white;
&:hover {
text-decoration: none;
}
}
}
&.action-bar {
width: 90%;
margin: 8px auto 0 auto;
}
}
input {
width: 98%;
text-align: center;
border: none;
padding: 0.5em 0;
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
border-radius: 0px;
margin: 5px auto 10px auto;
display: block;
}
textarea {
background: rgba(64, 64, 64, 0.95);
border: none;
padding: 0.5em;
display: block;
width: 90%;
margin: 0em auto;
resize: none;
color: white;
height: 200px;
}
.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: 50px;
left: 5%;
div {
margin-right:5px;
}
span {
line-height:20px;
float:left;
}
button {
padding-left:5px;
}
}
}
}
@media (max-width: 979px) {
.post-window {
position: relative;
bottom: 0px !important;
> div {
position: static;
width: 100% !important;
}
}
}
#mobile-menu {
position: fixed;
bottom: 0px;
height: 50px;
background: #333;
width: 100%;
left: 0px;
}
#mobile-menu, #mobile-menu-overlay {
z-index: 999;
@media (min-width: 979px)
{
display: none !important;
}
}
.btn-none,
.btn-none:active,
.btn-none[disabled] {
background-color: transparent;
background-image: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.btn-none {
cursor: pointer;
border-color: transparent;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.btn-none:hover,
.btn-none:focus {
text-decoration: none;
background-color: transparent;
}
.btn-none[disabled]:hover,
.btn-none[disabled]:focus {
text-decoration: none;
}
.btn-none {
.icon-white {
color: white;
}
}
#mobile-menu {
button {
color: #eee;
padding: 10px;
text-shadow: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
}
#mobile-menu-overlay {
background: rgba(0, 0, 0, 0.85);
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
//margin-top: 50px;
padding-top: 20px;
opacity: 0;
-webkit-transition: opacity 150ms ease;
-moz-transition: opacity 150ms ease;
-ms-transition: opacity 150ms ease;
-o-transition: opacity 150ms ease;
transition: opacity 150ms ease;
z-index: -1;
&.menu-visible {
z-index: 99;
opacity: 1;
}
.mobile-menu-icon {
color: white;
width: 20%;
min-width: 85px;
height: auto;
text-align: center;
padding: 10px;
display: inline-block;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-transition: margin-top 250ms ease;
-moz-transition: margin-top 250ms ease;
-ms-transition: margin-top 250ms ease;
-o-transition: margin-top 250ms ease;
transition: margin-top 250ms ease;
margin-top: 20%;
&.menu-visible {
margin-top: 0%;
}
i {
width: 100%;
}
}
}
#mobile-sidebar {
height: 100%;
position: absolute;
left: 100%;
top: 0px;
overflow: hidden;
margin-top: 60px;
}
.category-box {
height:90px;
.post-preview {
padding-left:10px;
padding-right:10px;
text-align:left;
img {
width:60px;
height:60px;
padding-right:5px;
}
p {
overflow: hidden;
text-overflow:ellipsis;
height:60px;
}
}
}
#mark-allread-btn {
margin-bottom:15px;
}
@-webkit-keyframes scroll-2 /* Safari and Chrome */
{
0% {top: 0px;}
25% {top: -90px;}
50% {top: -180px;}
75% {top: -270px;}
100% {top: -360px;}
}
@keyframes scroll-2
{
0% {top: 0px;}
25% {top: -90px;}
50% {top: -180px;}
75% {top: -270px;}
100% {top: -360px;}
}
@-webkit-keyframes scroll-1 /* Safari and Chrome */
{
0% {top: 0px;}
33% {top: -90px;}
66% {top: -180px;}
100% {top: -270px;}
}
@keyframes scroll-1
{
0% {top: 0px;}
33% {top: -90px;}
66% {top: -180px;}
100% {top: -270px;}
}
@-webkit-keyframes scroll-0 /* Safari and Chrome */
{
0% {top: 0px;}
50% {top: -90px;}
100% {top: -180px;}
}
@keyframes scroll-0
{
0% {top: 0px;}
50% {top: -90px;}
100% {top: -180px;}
}
.category-slider-2:hover {
position:relative;
-webkit-animation: scroll-2 10s ease 0.5s infinite normal;
animation: scroll-2 10s ease 0.5s infinite normal;/* Safari and Chrome: */
}
.category-slider-1:hover {
position:relative;
-webkit-animation: scroll-1 8s ease 0.5s infinite normal;
animation: scroll-1 8s ease 0.5s infinite normal;/* Safari and Chrome: */
}
.category-slider-0:hover {
position:relative;
-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

@@ -1,226 +0,0 @@
.post-container {
list-style-type: none;
padding: 0;
margin: 0;
clear: both;
.profile-image-block {
display: inline-block;
text-align: center;
font-size: 12px;
.stats {
clear: both;
}
}
li {
padding-bottom: 15px;
&.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;
p {
line-height: 1.75em;
}
}
.profile-block {
background: rgba(0, 0, 0, 0.02);
margin-right: -11px;
margin-left: -11px;
margin-bottom: -11px;
margin-top: 15px;
border-radius: 0 0 5px 5px;
font-size: 10px;
line-height: 18px;
padding: 5px;
padding-left: 10px;
img.hidden-desktop {
max-width: 10px;
max-height: 10px;
padding-top: 5px;
margin-right: 5px;
}
}
.post-content {
min-height: 50px;
padding: 2px 5px 0 5px;
word-wrap: break-word;
}
.post-images{
padding: 2px 5px 0 5px;
}
.post-block {
.post-buttons {
font-size: 12px;
float: right;
margin-right: 5px;
button, a {
display: inline-block;
padding: 0px 15px;
border: none;
border-left: 1px solid rgba(0, 0, 0, 0.06);
cursor: pointer;
background: none;
font-size: 12px;
height: 20px;
&:last-child {
padding-right: 5px;
}
}
.icon-star {
//theme this to make it yellow eventually
}
}
}
&.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;
overflow: hidden;
margin: 0;
padding: 0;
padding-top: 5px;
margin-bottom: -5px;
padding-bottom: 5px;
}
}
.main-avatar {
color: white;
position: relative;
float: left;
margin-right: 25px;
margin-bottom: 0px;
padding-bottom: 0px;
text-align: center;
width:80px;
@media (max-width: 767px) {
display: none;
}
}
.main-avatar:hover .hover-overlay {
opacity: 0.75;
}
.hover-overlay {
margin: 5px;
position: absolute;
bottom: 0px;
height: 35px;
padding-top: 2px;
width: 80px;
font-size: 13px;
line-height: 16px;
background: #000;
opacity: 0;
transition: opacity 0.3s;
}
.post-content {
min-height: 80px;
}
hr {
margin-top: 0;
margin-right: 10px;
margin-bottom: 0;
}
.post-block {
.post-buttons {
div {
border: 0;
}
}
}
.favourite {
cursor: pointer;
}
.btn {
display: inline-block;
}
.topic-buttons {
margin-top: 8px;
}
}
}

3
public/language/TODO Normal file
View File

@@ -0,0 +1,3 @@
NPM INSTALL
For now, language packs will be stored here. Eventually, will be moved to server side to allow for npm install-ability.
When that happens, server code will generate compressed JSON language files in this folder.

View File

@@ -0,0 +1,7 @@
{
"new_topic_button": "New Topic",
"no_topics": "<strong>There are no topics in this category.</strong><br />Why don't you try posting one?",
"sidebar.recent_replies": "Recent Replies",
"sidebar.active_participants": "Active Participants",
"sidebar.moderators": "Moderators"
}

View File

@@ -0,0 +1,9 @@
{
"chat.chatting_with": "Chat with <span id='chat-with-name'></span>",
"chat.placeholder": "type chat message, here press enter to send",
"chat.send": "Send",
"stats.online": "Online",
"stats.users": "Users",
"stats.topics": "Topics",
"stats.posts": "Posts"
}

View File

@@ -0,0 +1,17 @@
{
"home": "Home",
"search": "Search",
"buttons.close": "Close",
"403.title": "Access Denied",
"403.message": "You seem to have stumbled upon a page that you do not have access to. Perhaps you should <a href='/login'>try logging in</a>?",
"404.title": "Not Found",
"404.message": "You seem to have stumbled upon a page that does not exist. Return to the <a href='/''>home page</a>.",
"logout": "Logout",
"logout.title": "You are now logged out.",
"logout.message": "You have successfully logged out of NodeBB",
"header.recent": "Recent",
"header.unread": "Unread",
"header.users": "Users",
"header.search": "Search",
"notifications.loading": "Loading Notifications"
}

View File

@@ -0,0 +1,10 @@
{
"login": "Login",
"username": "Username",
"password": "Password",
"remember_me": "Remember Me?",
"forgot_password": "Forgot Password?",
"alternative_logins": "Alternative Logins",
"failed_login_attempt": "Failed login attempt, please try again.",
"login_successful": "You have successfully logged in!"
}

View File

@@ -0,0 +1,16 @@
{
"register": "Register",
"help.email": "By default, your email will be hidden from the public.",
"help.username_restrictions": "A unique username between %1 and %2 characters. Others can mention you with @<span id='yourUsername'>username</span>.",
"help.minimum_password_length": "Your password's length must be at least %1 characters.",
"email_address": "Email Address",
"email_address_placeholder": "Enter Email Address",
"username": "Username",
"username_placeholder": "Enter Username",
"password": "Password",
"password_placeholder": "Enter Password",
"confirm_password": "Confirm Password",
"confirm_password_placeholder": "Confirm Password",
"register_now_button": "Register Now",
"alternative_registration": "Alternative Registration"
}

View File

@@ -0,0 +1,22 @@
{
"profile": "Profile",
"posted_by": "Posted by",
"chat": "Chat",
"notify_me": "Be notified of new replies in this topic",
"favourite": "Favourite",
"quote": "Quote",
"reply": "Reply",
"edit": "Edit",
"delete": "Delete",
"banned": "banned",
"link": "Link",
"thread_tools.title": "Thread Tools",
"thread_tools.pin": "Pin Thread",
"thread_tools.lock": "Lock Thread",
"thread_tools.move": "Move Thread",
"thread_tools.delete": "Delete Thread",
"load_categories": "Loading Categories",
"confirm_move": "Move",
"favourites.not_logged_in.title": "Not Logged In",
"favourites.not_logged_in.message": "Please log in in order to favourite this post"
}

View File

@@ -0,0 +1,5 @@
{
"no_unread_topics": "There are no unread topics.",
"mark_all_read": "Mark all as Read",
"load_more": "Load More"
}

View File

@@ -0,0 +1,9 @@
{
"latest_users": "Latest Users",
"top_posters": "Top Posters",
"most_reputation": "Most Reputation",
"online": "Online",
"search": "Search",
"enter_username": "Enter a username to search",
"load_more": "Load More"
}

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,8 @@
var ajaxify = {};
(function($) {
(function ($) {
/*global app, templates, utils*/
var location = document.location || window.location,
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
@@ -11,8 +12,8 @@ var ajaxify = {};
var executed = {};
var events = [];
ajaxify.register_events = function(new_page_events) {
for (var i = 0, ii = events.length; i<ii; i++) {
ajaxify.register_events = function (new_page_events) {
for (var i = 0, ii = events.length; i < ii; i++) {
socket.removeAllListeners(events[i]); // optimize this to user removeListener(event, listener) instead.
}
@@ -20,28 +21,36 @@ var ajaxify = {};
};
window.onpopstate = function(event) {
window.onpopstate = function (event) {
// "quiet": If set to true, will not call pushState
if (event !== null && event.state && event.state.url !== undefined) ajaxify.go(event.state.url, null, null, true);
};
ajaxify.go = function(url, callback, template, quiet) {
var pagination;
ajaxify.go = function (url, callback, template, quiet) {
// start: the following should be set like so: ajaxify.onchange(function(){}); where the code actually belongs
$(window).off('scroll');
// leave room and join global
app.enter_room('global');
var url = url.replace(/\/$/, "");
pagination = pagination || document.getElementById('pagination');
if (pagination) pagination.parentNode.style.display = 'none';
window.onscroll = null;
// end
// Remove trailing slash
url = url.replace(/\/$/, "");
var hash = window.location.hash;
if(url.indexOf(RELATIVE_PATH.slice(1)) !== -1) {
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 === '/') {
if (url === '' || url === '/') {
tpl_url = 'home';
} else {
tpl_url = url.split('/')[0].split('?')[0];
@@ -53,27 +62,31 @@ var ajaxify = {};
if (templates.is_available(tpl_url) && !templates.force_refresh(tpl_url)) {
if (quiet !== true) {
window.history.pushState({
"url": url
}, url, RELATIVE_PATH + "/" + url);
if (window.history && window.history.pushState) {
window.history.pushState({
"url": url
}, url, RELATIVE_PATH + "/" + url);
}
}
translator.load(tpl_url);
jQuery('#footer, #content').fadeOut(100);
templates.flush();
templates.load_template(function() {
templates.load_template(function () {
exec_body_scripts(content);
if (callback) {
callback();
}
app.process_page();
jQuery('#content, #footer').stop(true, true).fadeIn(200, function () {
jQuery('#content, #footer').stop(true, true).fadeIn(200, function() {
if(window.location.hash)
app.process_page();
if (window.location.hash)
hash = window.location.hash;
if(hash)
if (hash)
app.scrollToPost(hash.substr(1));
});
@@ -85,53 +98,60 @@ var ajaxify = {};
}
return false;
}
};
$('document').ready(function() {
$('document').ready(function () {
if (!window.history || !window.history.pushState) return; // no ajaxification for old browsers
content = content || document.getElementById('content');
// Enhancing all anchors to ajaxify...
$(document.body).on('click', 'a', function(e) {
$(document.body).on('click', 'a', function (e) {
function hrefEmpty(href) {
return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#";
}
if(hrefEmpty(this.href)) return;
if (hrefEmpty(this.href) || this.target !== '' || this.protocol === 'javascript:')
return;
var url = this.href.replace(rootUrl +'/', '');
if (this.target !== '') return;
app.previousUrl = window.location.href;
if (!e.ctrlKey && e.which === 1) {
if (ajaxify.go(url)) {
if (this.host === window.location.host) {
// Internal link
var url = this.href.replace(rootUrl + '/', '');
if (ajaxify.go(url)) {
e.preventDefault();
}
} else if (window.location.pathname !== '/outgoing') {
// External Link
ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
e.preventDefault();
}
}
});
});
function exec_body_scripts(body_el) {
// modified from http://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
function nodeName(elem, name) {
return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
};
}
function evalScript(elem) {
var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
head = document.getElementsByTagName("head")[0] ||
document.documentElement,
var data = (elem.text || elem.textContent || elem.innerHTML || ""),
head = document.getElementsByTagName("head")[0] ||
document.documentElement,
script = document.createElement("script");
script.type = "text/javascript";
try {
script.appendChild(document.createTextNode(data));
} catch(e) {
script.text = data;
script.appendChild(document.createTextNode(data));
} catch (e) {
script.text = data;
}
if (elem.src) {
@@ -141,18 +161,18 @@ var ajaxify = {};
head.insertBefore(script, head.firstChild);
//TODO: remove from head before inserting?, doing this breaks scripts in safari so commented out for now
//head.removeChild(script);
};
}
var scripts = [],
script,
children_nodes = body_el.childNodes,
children_nodes = $(body_el).children(),
child,
i;
for (i = 0; children_nodes[i]; i++) {
child = children_nodes[i];
if (nodeName(child, "script" ) &&
(!child.type || child.type.toLowerCase() === "text/javascript")) {
if (nodeName(child, "script") &&
(!child.type || child.type.toLowerCase() === "text/javascript")) {
scripts.push(child);
}
}
@@ -164,6 +184,6 @@ var ajaxify = {};
}
evalScript(scripts[i]);
}
};
}
}(jQuery));

View File

@@ -1,119 +1,118 @@
var socket,
config,
app = {},
API_URL = null;
app = {};
(function() {
(function () {
var showWelcomeMessage = false;
function loadConfig() {
app.loadConfig = function() {
$.ajax({
url: RELATIVE_PATH + '/api/config',
success: function(data) {
API_URL = data.api_url;
success: function (data) {
config = data;
socket = io.connect(config.socket.address + (config.socket.port ? ':' + config.socket.port : ''));
if(socket) {
socket.disconnect();
setTimeout(function() {
socket.socket.connect();
}, 200);
} else {
socket = io.connect(config.socket.address);
var reconnecting = false;
var reconnectTries = 0;
var reconnecting = false,
reconnectEl, reconnectTimer;
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('connect', function(data){
if(reconnecting) {
setTimeout(function(){
app.alert({
alert_id: 'connection_alert',
title: 'Connected',
message: 'Connection successful.',
type: 'success',
timeout: 5000
});
}, 1000);
reconnecting = false;
reconnectTries = 0;
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
}
});
socket.on('reconnecting', function(data) {
function showDisconnectModal() {
$('#disconnect-modal').modal({
backdrop:'static',
show:true
socket.on('event:connect', function (data) {
app.username = data.username;
app.showLoginMessage();
socket.emit('api:updateHeader', {
fields: ['username', 'picture', 'userslug']
});
$('#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',
message: 'You have disconnected from NodeBB, we will try to reconnect you. <br/><i class="icon-refresh icon-spin"></i>',
type: 'notify',
timeout: 5000
});
});
socket.on('api:user.get_online_users', function(users) {
jQuery('.username-field').each(function() {
if (this.processed === true)
return;
socket.on('event:alert', function (data) {
app.alert(data);
});
var el = jQuery(this),
uid = el.parents('li').attr('data-uid');
socket.on('connect', function (data) {
if (reconnecting) {
reconnectEl.html('<i class="icon-ok"></i> Connected!');
reconnecting = false;
if (uid && jQuery.inArray(uid, users) !== -1) {
el.find('i').remove();
el.prepend('<i class="icon-circle"></i>');
} else {
el.find('i').remove();
el.prepend('<i class="icon-circle-blank"></i>');
setTimeout(function() {
reconnectEl.removeClass('active');
}, 3000);
}
el.processed = true;
socket.emit('api:updateHeader', {
fields: ['username', 'picture', 'userslug']
});
});
});
app.enter_room('global');
socket.on('event:disconnect', function() {
socket.socket.connect();
});
socket.on('reconnecting', function (data) {
if (!reconnectEl) reconnectEl = $('#reconnect');
reconnecting = true;
reconnectEl.addClass('active');
reconnectEl.html('<i class="icon-spinner icon-spin"></i> Reconnecting...');
});
socket.on('api:user.get_online_users', function (users) {
jQuery('a.username-field').each(function () {
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>');
} else {
el.find('i').remove();
el.prepend('<i class="icon-circle-blank"></i>');
}
el.processed = true;
});
jQuery('button .username-field').each(function () {
//DRY FAIL
if (this.processed === true)
return;
var el = jQuery(this),
uid = el.parents('li').attr('data-uid');
if (uid && jQuery.inArray(uid, users) !== -1) {
el.parent().addClass('btn-success');
} else {
el.parent().addClass('btn-danger');
}
el.processed = true;
});
});
app.enter_room('global');
}
},
async: false
});
}
// takes a string like 1000 and returns 1,000
app.addCommas = function(text) {
app.addCommas = function (text) {
return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
}
// Willingly stolen from: http://phpjs.org/functions/strip_tags/
app.strip_tags = function(input, allowed) {
app.strip_tags = function (input, allowed) {
allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
@@ -127,14 +126,14 @@ var socket,
// message = alert message content
// timeout default = permanent
// location : alert_window (default) or content
app.alert = function(params) {
app.alert = function (params) {
var alert_id = 'alert_button_' + ((params.alert_id) ? params.alert_id : new Date().getTime());
var alert = $('#'+alert_id);
var alert = $('#' + alert_id);
function startTimeout(div, timeout) {
var timeoutId = setTimeout(function() {
$(div).fadeOut(1000, function() {
var timeoutId = setTimeout(function () {
$(div).fadeOut(1000, function () {
$(this).remove();
});
}, timeout);
@@ -142,15 +141,14 @@ var socket,
$(div).attr('timeoutId', timeoutId);
}
if(alert.length > 0) {
if (alert.length > 0) {
alert.find('strong').html(params.title);
alert.find('p').html(params.message);
alert.attr('class', "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type));
alert.attr('class', "alert toaster-alert " + "alert-" + params.type);
clearTimeout(alert.attr('timeoutId'));
startTimeout(alert, params.timeout);
}
else {
} else {
var div = document.createElement('div'),
button = document.createElement('button'),
strong = document.createElement('strong'),
@@ -159,7 +157,7 @@ var socket,
p.innerHTML = params.message;
strong.innerHTML = params.title;
div.className = "alert toaster-alert " + ((params.type=='warning') ? '' : "alert-" + params.type);
div.className = "alert toaster-alert " + "alert-" + params.type;
div.setAttribute('id', alert_id);
div.appendChild(button);
@@ -168,23 +166,23 @@ var socket,
button.className = 'close';
button.innerHTML = '&times;';
button.onclick = function(ev) {
button.onclick = function (ev) {
div.parentNode.removeChild(div);
}
if (params.location == null)
params.location = 'alert_window';
jQuery('#'+params.location).prepend(jQuery(div).fadeIn('100'));
jQuery('#' + params.location).prepend(jQuery(div).fadeIn('100'));
if (params.timeout) {
startTimeout(div, params.timeout);
}
if (params.clickfn) {
div.onclick = function() {
div.onclick = function () {
params.clickfn();
jQuery(div).fadeOut(500, function() {
jQuery(div).fadeOut(500, function () {
this.remove();
});
}
@@ -192,8 +190,8 @@ var socket,
}
}
app.alertSuccess = function(message, timeout) {
if(!timeout)
app.alertSuccess = function (message, timeout) {
if (!timeout)
timeout = 2000;
app.alert({
@@ -204,21 +202,21 @@ var socket,
});
}
app.alertError = function(message, timeout) {
if(!timeout)
app.alertError = function (message, timeout) {
if (!timeout)
timeout = 2000;
app.alert({
title: 'Error',
message: message,
type: 'error',
type: 'danger',
timeout: timeout
});
}
app.current_room = null;
app.enter_room = function(room) {
if(socket) {
app.enter_room = function (room) {
if (socket) {
if (app.current_room === room)
return;
@@ -231,20 +229,20 @@ var socket,
}
};
app.populate_online_users = function() {
app.populate_online_users = function () {
var uids = [];
jQuery('.post-row').each(function() {
jQuery('.post-row').each(function () {
uids.push(this.getAttribute('data-uid'));
});
socket.emit('api:user.get_online_users', uids);
}
app.process_page = function() {
app.process_page = function () {
// here is where all modules' onNavigate should be called, I think.
require(['mobileMenu'], function(mobileMenu) {
require(['mobileMenu'], function (mobileMenu) {
mobileMenu.onNavigate();
});
@@ -252,13 +250,13 @@ var socket,
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length-1];
active = parts[parts.length - 1];
jQuery('#main-nav li').removeClass('active');
if(active) {
jQuery('#main-nav li a').each(function() {
if (active) {
jQuery('#main-nav li a').each(function () {
var href = this.getAttribute('href');
if(active.match(/^users/))
if (active == "sort-posts" || active == "sort-reputation" || active == "search" || active == "latest" || active == "online")
active = 'users';
if (href && href.match(active)) {
jQuery(this.parentNode).addClass('active');
@@ -267,12 +265,15 @@ var socket,
});
}
setTimeout(function() {
$('span.timeago').timeago();
setTimeout(function () {
window.scrollTo(0, 1); // rehide address bar on mobile after page load completes.
}, 100);
}
app.showLoginMessage = function() {
app.showLoginMessage = function () {
function showAlert() {
app.alert({
type: 'success',
@@ -282,9 +283,9 @@ var socket,
});
}
if(showWelcomeMessage) {
if (showWelcomeMessage) {
showWelcomeMessage = false;
if(document.readyState !== 'complete') {
if (document.readyState !== 'complete') {
$(document).ready(showAlert);
} else {
showAlert();
@@ -292,56 +293,90 @@ var socket,
}
}
app.addCommasToNumbers = function() {
$('.formatted-number').each(function(index, element) {
app.addCommasToNumbers = function () {
$('.formatted-number').each(function (index, element) {
$(element).html(app.addCommas($(element).html()));
});
}
app.createNewPosts = function(data) {
app.openChat = function (username, touid) {
require(['chat'], function (chat) {
var chatModal;
if (!chat.modalExists(touid)) {
chatModal = chat.createModal(username, touid);
} else {
chatModal = chat.getModal(touid);
}
chat.load(chatModal.attr('UUID'));
});
}
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');
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
translator.translate(html, function(translatedHTML) {
var uniqueid = new Date().getTime(),
tempContainer = jQuery('<div id="' + uniqueid + '"></div>')
.appendTo("#post-container")
.hide()
.append(translatedHTML)
.fadeIn('slow');
for(var x=0,numPosts=data.posts.length;x<numPosts;x++) {
socket.emit('api:post.privileges', data.posts[x].pid);
}
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;
tempContainer.replaceWith(tempContainer.contents());
infiniteLoaderActive = false;
app.populate_online_users();
app.addCommasToNumbers();
app.populate_online_users();
app.addCommasToNumbers();
$('span.timeago').timeago();
});
}
app.infiniteLoaderActive = false;
app.loadMorePosts = function(tid, callback) {
if(app.infiniteLoaderActive)
app.loadMorePosts = function (tid, callback) {
if (app.infiniteLoaderActive)
return;
app.infiniteLoaderActive = true;
if ($('#loading-indicator').attr('done') === '0')
$('#loading-indicator').removeClass('hide');
socket.emit('api:topic.loadMore', {
tid: tid,
after: document.querySelectorAll('#post-container li[data-pid]').length
}, function(data) {
}, function (data) {
app.infiniteLoaderActive = false;
if(data.posts.length) {
if (data.posts.length) {
$('#loading-indicator').attr('done', '0');
app.createNewPosts(data);
} else {
$('#loading-indicator').attr('done', '1');
}
if(callback)
$('#loading-indicator').addClass('hide');
if (callback)
callback(data.posts);
});
}
app.scrollToPost = function(pid) {
app.scrollToTop = function () {
$('body,html').animate({
scrollTop: 0
});
};
if(!pid)
app.scrollToBottom = function () {
$('body,html').animate({
scrollTop: $('html').height() - 100
});
}
app.scrollToPost = function (pid) {
if (!pid)
return;
var container = $(document.body),
@@ -351,96 +386,42 @@ var socket,
function animateScroll() {
$('body,html').animate({
scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height()
});
}, 400);
//$('body,html').scrollTop(scrollTo.offset().top - container.offset().top + container.scrollTop() - $('#header-menu').height());
}
if(!scrollTo.length && tid) {
if (!scrollTo.length && tid) {
var intervalID = setInterval(function() {
app.loadMorePosts(tid, function(posts) {
var intervalID = setInterval(function () {
app.loadMorePosts(tid, function (posts) {
scrollTo = $('#post_anchor_' + pid);
if(tid && scrollTo.length) {
if (tid && scrollTo.length) {
animateScroll();
}
if(!posts.length || scrollTo.length)
if (!posts.length || scrollTo.length)
clearInterval(intervalID);
});
}, 100);
} else if(tid) {
} else if (tid) {
animateScroll();
}
}
jQuery('document').ready(function() {
addTouchEvents();
$('#search-form').on('submit', function() {
jQuery('document').ready(function () {
$('#search-form').on('submit', function () {
var input = $(this).find('input');
ajaxify.go("search/"+input.val(), null, "search");
ajaxify.go("search/" + input.val(), null, "search");
input.val('');
return false;
})
});
});
showWelcomeMessage = location.href.indexOf('loggedin') !== -1;
loadConfig();
app.loadConfig();
function addTouchEvents() {
return; // later.
// click simulation just for testing/sanity purposes.
var el = jQuery("#content"),
sidebar = jQuery('#mobile-sidebar'),
width = el.width();
function onTouchMove(ev) {
var coordinates = window.event ? window.event.touches[0] : ev.touches[0];
el.css({
marginLeft: -parseInt(width - coordinates.pageX) + 'px',
paddingRight: parseInt(width - coordinates.pageX) + 'px'});
sidebar.css({
marginLeft: -parseInt(width - coordinates.pageX) + 'px',
width: parseInt(width - coordinates.pageX) + 'px'
});
}
function onMouseMove(ev) {
ev.touches = [{pageX: ev.pageX, pageY: ev.pageY}];
onTouchMove(ev);
}
function onTouchEnd() {
el.css({
marginLeft: '0px',
paddingRight: '0px'
});
sidebar.css({
marginLeft: '0px',
width: '0px'
});
}
el.on('touchmove', onTouchMove);
el.on('mousedown', function() {
el.on('mousemove', onMouseMove);
});
el.on('touchend', onTouchEnd);
el.on('mouseup', function() {
el.off('mousemove');
onTouchEnd();
});
}
}());
}());

View File

@@ -4,26 +4,32 @@
isFollowing = templates.get('isFollowing');
$(document).ready(function() {
var username = $('.account-username a').html();
app.enter_room('user/' + theirid);
app.addCommasToNumbers();
var followBtn = $('#follow-btn');
var unfollowBtn = $('#unfollow-btn');
if(yourid !== theirid) {
if(isFollowing) {
if (yourid !== theirid) {
if (isFollowing) {
followBtn.hide();
unfollowBtn.show();
} else {
followBtn.show();
unfollowBtn.hide();
}
} else {
followBtn.hide();
unfollowBtn.hide();
}
followBtn.on('click', function() {
socket.emit('api:user.follow', {uid: theirid}, function(success) {
var username = $('.account-username a').html();
if(success) {
socket.emit('api:user.follow', {
uid: theirid
}, function(success) {
if (success) {
followBtn.hide();
unfollowBtn.show();
app.alertSuccess('You are now following ' + username + '!');
@@ -35,9 +41,10 @@
});
unfollowBtn.on('click', function() {
socket.emit('api:user.unfollow', {uid: theirid}, function(success) {
var username = $('.account-username a').html();
if(success) {
socket.emit('api:user.unfollow', {
uid: theirid
}, function(success) {
if (success) {
followBtn.show();
unfollowBtn.hide();
app.alertSuccess('You are no longer following ' + username + '!');
@@ -51,20 +58,27 @@
$('.user-recent-posts .topic-row').on('click', function() {
ajaxify.go($(this).attr('topic-url'));
});
var onlineStatus = $('.account-online-status');
socket.on('api:user.isOnline', function(online) {
if(online) {
function handleUserOnline(data) {
if (data.online) {
onlineStatus.find('span span').text('online');
onlineStatus.find('i').attr('class', 'icon-circle');
} else {
onlineStatus.find('span span').text('offline');
onlineStatus.find('i').attr('class', 'icon-circle-blank');
}
}
socket.on('api:user.isOnline', handleUserOnline);
socket.emit('api:user.isOnline', theirid, handleUserOnline);
socket.on('event:new_post', function(data) {
var html = templates.prepare(templates['account'].blocks['posts']).parse(data);
$('.user-recent-posts').prepend(html);
});
socket.emit('api:user.isOnline', theirid);
});

View File

@@ -1,212 +1,214 @@
var gravatarPicture = templates.get('gravatarpicture');
var uploadedPicture = templates.get('uploadedpicture');
$(document).ready(function() {
$('#uploadForm').submit(function() {
status('uploading the file ...');
$('#upload-progress-bar').css('width', '0%');
$('#upload-progress-box').show();
if(!$('#userPhotoInput').val()) {
$('#upload-progress-box').removeClass('hide');
if (!$('#userPhotoInput').val()) {
error('select an image to upload!');
return false;
return false;
}
$(this).find('#imageUploadCsrf').val($('#csrf_token').val());
$(this).ajaxSubmit({
error: function(xhr) {
error('Error: ' + xhr.status);
},
uploadProgress : function(event, position, total, percent) {
$('#upload-progress-bar').css('width', percent+'%');
uploadProgress: function(event, position, total, percent) {
console.log(percent);
$('#upload-progress-bar').css('width', percent + '%');
},
success: function(response) {
if(response.error) {
if (response.error) {
error(response.error);
return;
}
var imageUrlOnServer = response.path;
$('#user-current-picture').attr('src', imageUrlOnServer);
$('#user-uploaded-picture').attr('src', imageUrlOnServer);
uploadedPicture = imageUrlOnServer;
uploadedPicture = imageUrlOnServer;
setTimeout(function() {
hideAlerts();
$('#upload-picture-modal').modal('hide');
}, 750);
socket.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
socket.emit('api:updateHeader', {
fields: ['username', 'picture', 'userslug']
});
success('File uploaded successfully!');
}
});
return false;
});
function hideAlerts() {
$('#alert-status').hide();
$('#alert-success').hide();
$('#alert-error').hide();
$('#upload-progress-box').hide();
$('#alert-status').addClass('hide');
$('#alert-success').addClass('hide');
$('#alert-error').addClass('hide');
$('#upload-progress-box').addClass('hide');
}
function status(message) {
hideAlerts();
$('#alert-status').text(message).show();
$('#alert-status').text(message).removeClass('hide');
}
function success(message) {
hideAlerts();
$('#alert-success').text(message).show();
$('#alert-success').text(message).removeClass('hide');
}
function error(message) {
hideAlerts();
$('#alert-error').text(message).show();
$('#alert-error').text(message).removeClass('hide');
}
function changeUserPicture(type) {
function changeUserPicture(type) {
var userData = {
type: type
};
socket.emit('api:user.changePicture', userData, function(success) {
if(!success) {
if (!success) {
app.alertError('There was an error changing picture!');
}
});
}
var selectedImageType = '';
$('#submitBtn').on('click',function(){
var userData = {
uid:$('#inputUID').val(),
email:$('#inputEmail').val(),
fullname:$('#inputFullname').val(),
website:$('#inputWebsite').val(),
birthday:$('#inputBirthday').val(),
location:$('#inputLocation').val(),
signature:$('#inputSignature').val()
$('#submitBtn').on('click', function() {
var userData = {
uid: $('#inputUID').val(),
email: $('#inputEmail').val(),
fullname: $('#inputFullname').val(),
website: $('#inputWebsite').val(),
birthday: $('#inputBirthday').val(),
location: $('#inputLocation').val(),
signature: $('#inputSignature').val()
};
socket.emit('api:user.updateProfile', userData, function(data) {
if(data.success) {
socket.emit('api:user.updateProfile', userData, function(err, data) {
if (data.success) {
app.alertSuccess('Your profile has been updated successfully!');
if(data.picture) {
if (data.picture) {
$('#user-current-picture').attr('src', data.picture);
$('#user_label img').attr('src', data.picture);
}
if(data.gravatarpicture) {
if (data.gravatarpicture) {
$('#user-gravatar-picture').attr('src', data.gravatarpicture);
gravatarPicture = data.gravatarpicture;
}
}
} else {
app.alertError('There was an error updating your profile!');
app.alertError('There was an error updating your profile! ' + err.error);
}
});
return false;
});
function updateImages() {
var currentPicture = $('#user-current-picture').attr('src');
if(gravatarPicture) {
if (gravatarPicture) {
$('#user-gravatar-picture').attr('src', gravatarPicture);
$('#gravatar-box').show();
}
else
} else
$('#gravatar-box').hide();
if(uploadedPicture) {
if (uploadedPicture) {
$('#user-uploaded-picture').attr('src', uploadedPicture);
$('#uploaded-box').show();
}
else
} else
$('#uploaded-box').hide();
if(currentPicture == gravatarPicture)
if (currentPicture == gravatarPicture)
$('#gravatar-box .icon-ok').show();
else
$('#gravatar-box .icon-ok').hide();
if(currentPicture == uploadedPicture)
if (currentPicture == uploadedPicture)
$('#uploaded-box .icon-ok').show();
else
$('#uploaded-box .icon-ok').hide();
}
$('#changePictureBtn').on('click', function() {
selectedImageType = '';
updateImages();
$('#change-picture-modal').modal('show');
$('#change-picture-modal').removeClass('hide');
return false;
});
$('#gravatar-box').on('click', function(){
$('#gravatar-box').on('click', function() {
$('#gravatar-box .icon-ok').show();
$('#uploaded-box .icon-ok').hide();
selectedImageType = 'gravatar';
});
$('#uploaded-box').on('click', function(){
$('#uploaded-box').on('click', function() {
$('#gravatar-box .icon-ok').hide();
$('#uploaded-box .icon-ok').show();
selectedImageType = 'uploaded';
});
$('#savePictureChangesBtn').on('click', function() {
$('#change-picture-modal').modal('hide');
if(selectedImageType) {
if (selectedImageType) {
changeUserPicture(selectedImageType);
if(selectedImageType == 'gravatar')
$('#user-current-picture').attr('src', gravatarPicture);
else if(selectedImageType == 'uploaded')
$('#user-current-picture').attr('src', uploadedPicture);
if (selectedImageType == 'gravatar')
$('#user-current-picture').attr('src', gravatarPicture);
else if (selectedImageType == 'uploaded')
$('#user-current-picture').attr('src', uploadedPicture);
}
});
$('#upload-picture-modal').on('hide', function() {
$('#userPhotoInput').val('');
});
$('#uploadPictureBtn').on('click', function(){
$('#uploadPictureBtn').on('click', function() {
$('#change-picture-modal').modal('hide');
$('#upload-picture-modal').modal('show');
$('#upload-picture-modal').removeClass('hide');
hideAlerts();
return false;
});
$('#pictureUploadSubmitBtn').on('click', function() {
$('#uploadForm').submit();
});
(function handlePasswordChange() {
var currentPassword = $('#inputCurrentPassword');
var password_notify = $('#password-notify');
@@ -219,59 +221,71 @@ $(document).ready(function() {
function onPasswordChanged() {
passwordvalid = utils.isPasswordValid(password.val());
if (password.val().length < 6) {
if (password.val().length < config.minimumPasswordLength) {
password_notify.html('Password too short');
password_notify.attr('class', 'label label-important');
} else if(!passwordvalid) {
password_notify.attr('class', 'alert alert-danger');
password_notify.removeClass('hide');
} else if (!passwordvalid) {
password_notify.html('Invalid password');
password_notify.attr('class', 'label label-important');
password_notify.attr('class', 'alert alert-danger');
password_notify.removeClass('hide');
} else {
password_notify.html('OK!');
password_notify.attr('class', 'label label-success');
password_notify.attr('class', 'alert alert-success');
password_notify.removeClass('hide');
}
onPasswordConfirmChanged();
}
function onPasswordConfirmChanged() {
if(password.val() !== password_confirm.val()) {
if (password_notify.hasClass('alert-danger') || !password_confirm.val()) {
password_confirm_notify.addClass('hide');
return;
}
if (password.val() !== password_confirm.val()) {
password_confirm_notify.html('Passwords must match!');
password_confirm_notify.attr('class', 'label label-important');
password_confirm_notify.attr('class', 'alert alert-danger');
password_confirm_notify.removeClass('hide');
passwordsmatch = false;
} else {
password_confirm_notify.html('OK!');
password_confirm_notify.attr('class', 'label label-success');
password_confirm_notify.attr('class', 'alert alert-success');
password_confirm_notify.removeClass('hide');
passwordsmatch = true;
}
}
password.on('keyup', onPasswordChanged);
password_confirm.on('keyup', onPasswordConfirmChanged);
password.on('blur', onPasswordChanged);
password_confirm.on('blur', onPasswordConfirmChanged);
$('#changePasswordBtn').on('click', function() {
if(passwordvalid && passwordsmatch && currentPassword.val()) {
socket.emit('api:user.changePassword', {'currentPassword': currentPassword.val(),'newPassword': password.val() }, function(data) {
if (passwordvalid && passwordsmatch && currentPassword.val()) {
socket.emit('api:user.changePassword', {
'currentPassword': currentPassword.val(),
'newPassword': password.val()
}, function(err) {
currentPassword.val('');
password.val('');
password_confirm.val('');
password_notify.html('');
password_confirm_notify.html('');
password_notify.addClass('hide');
password_confirm_notify.addClass('hide');
passwordsmatch = false;
passwordvalid = false;
if(data.err) {
app.alertError(data.err);
if (err) {
app.alertError(err.error);
return;
}
app.alertSuccess('Your password is updated!');
});
}
return false;
});
}());
});

View File

@@ -6,31 +6,37 @@
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>\
<span id="settingsLink" class="pull-right"><a href="/user/' + userslug + '/settings">settings</a></span>\
<span id="favouritesLink" class="pull-right"><a href="/user/' + userslug + '/favourites">favourites</a></span>\
<span class="pull-right"><a href="/user/' + userslug + '/followers">followers</a></span>\
<span class="pull-right"><a href="/user/' + userslug + '/following">following</a></span>\
<span id="editLink" class="pull-right"><a href="/user/' + 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" || yourid !== theirid) {
if (yourid === "0" || yourid !== theirid) {
editLink.hide();
settingsLink.hide();
favouritesLink.hide();
}
jQuery('.account-sub-links span a').removeClass('bold').each(function() {
var href = this.getAttribute('href');
if (window.location.href.indexOf(href) !== -1) {
jQuery(this).addClass('bold');
return false;
}
});
});
}());

View File

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

View File

@@ -1,11 +1,10 @@
var modified_categories = {};
function modified(el) {
var cid = $(el).parents('li').attr('data-cid');
modified_categories[cid] = modified_categories[cid] || {};
modified_categories[cid][el.getAttribute('data-name')] = el.value;
modified_categories[cid][$(el).attr('data-name')] = $(el).val();
}
function save() {
@@ -14,25 +13,28 @@ function save() {
}
function select_icon(el) {
var selected = el.className.replace(' icon-2x', '');
var selected = el.attr('class').replace(' icon-2x', '');
jQuery('#icons .selected').removeClass('selected');
jQuery('#icons .' + selected).parent().addClass('selected');
if (selected)
jQuery('#icons .' + selected).parent().addClass('selected');
bootbox.confirm('<h2>Select an icon.</h2>' + document.getElementById('icons').innerHTML, function(confirm) {
if (confirm) {
var iconClass = jQuery('.bootbox .selected').children(':first').attr('class');
el.className = iconClass + ' icon icon-2x';
el.value = iconClass;
el.attr('class', iconClass + ' icon-2x');
el.val(iconClass);
modified(el);
}
});
jQuery('.bootbox .span3').on('click', function() {
jQuery('.bootbox .selected').removeClass('selected');
jQuery(this).addClass('selected');
});
setTimeout(function() { //bootbox was rewritten for BS3 and I had to add this timeout for the previous code to work. TODO: to look into
jQuery('.bootbox .col-md-3').on('click', function() {
jQuery('.bootbox .selected').removeClass('selected');
jQuery(this).addClass('selected');
});
}, 500);
}
@@ -46,12 +48,44 @@ jQuery('.blockclass').each(function() {
});
//DRY Failure. this needs to go into an ajaxify onready style fn. Currently is copy pasted into every single function so after ACP is off the ground fix asap
//DRY Failure. this needs to go into an ajaxify onready style fn. Currently is copy pasted into every single function so after ACP is off the ground fix asap
(function() {
function showCreateCategoryModal() {
$('#new-category-modal').modal();
}
function createNewCategory() {
var category = {
name: $('#inputName').val(),
description: $('#inputDescription').val(),
icon: $('#new-category-modal i').attr('value'),
blockclass: $('#inputBlockclass').val()
};
socket.emit('api:admin.categories.create', category, function(err, data) {
if (!err) {
app.alert({
alert_id: 'category_created',
title: 'Created',
message: 'Category successfully created!',
type: 'success',
timeout: 2000
});
var html = templates.prepare(templates['admin/categories'].blocks['categories']).parse({
categories: [data]
});
$('#entry-container').append(html);
$('#new-category-modal').modal('hide');
}
});
}
jQuery('document').ready(function() {
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length-1];
active = parts[parts.length - 1];
jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
@@ -62,9 +96,11 @@ jQuery('.blockclass').each(function() {
});
jQuery('#save').on('click', save);
jQuery('#addNew').on('click', showCreateCategoryModal);
jQuery('#create-category-btn').on('click', createNewCategory);
jQuery('.icon').on('click', function(ev) {
select_icon(ev.target);
jQuery('#entry-container').on('click', '.icon', function(ev) {
select_icon($(this).find('i'));
});
jQuery('.blockclass').on('change', function(ev) {
@@ -77,7 +113,7 @@ jQuery('.blockclass').each(function() {
jQuery('.entry-row button').each(function(index, element) {
var disabled = $(element).attr('data-disabled');
if(disabled == "0" || disabled == "")
if (disabled == "0" || disabled == "")
$(element).html('Disable');
else
$(element).html('Enable');
@@ -89,7 +125,7 @@ jQuery('.blockclass').each(function() {
var categoryRow = btn.parents('li');
var cid = categoryRow.attr('data-cid');
var disabled = btn.html() == "Disable" ? "1":"0";
var disabled = btn.html() == "Disable" ? "1" : "0";
categoryRow.remove();
modified_categories[cid] = modified_categories[cid] || {};
modified_categories[cid]['disabled'] = disabled;
@@ -99,5 +135,5 @@ jQuery('.blockclass').each(function() {
});
});
}());

View File

@@ -1,10 +1,7 @@
var nodebb_admin = (function(nodebb_admin) {
nodebb_admin.config = undefined;
nodebb_admin.prepare = function() {
// Come back in 500ms if the config isn't ready yet
if (nodebb_admin.config === undefined) {
@@ -19,21 +16,21 @@ var nodebb_admin = (function(nodebb_admin) {
numFields = fields.length,
saveBtn = document.getElementById('save'),
x, key, inputType;
for(x=0;x<numFields;x++) {
for (x = 0; x < numFields; x++) {
key = fields[x].getAttribute('data-field');
inputType = fields[x].getAttribute('type');
if (fields[x].nodeName === 'INPUT') {
if (nodebb_admin.config[key]) {
switch(inputType) {
switch (inputType) {
case 'text':
case 'textarea':
case 'number':
fields[x].value = nodebb_admin.config[key];
break;
break;
case 'checkbox':
fields[x].checked = nodebb_admin.config[key] === '1' ? true : false;
break;
break;
}
}
} else if (fields[x].nodeName === 'TEXTAREA') {
@@ -45,32 +42,35 @@ var nodebb_admin = (function(nodebb_admin) {
var key, value;
e.preventDefault();
for(x=0;x<numFields;x++) {
for (x = 0; x < numFields; x++) {
key = fields[x].getAttribute('data-field');
if (fields[x].nodeName === 'INPUT') {
inputType = fields[x].getAttribute('type');
switch(inputType) {
switch (inputType) {
case 'text':
case 'number':
value = fields[x].value;
break;
break;
case 'checkbox':
value = fields[x].checked ? '1' : '0';
break;
break;
}
} else if (fields[x].nodeName === 'TEXTAREA') {
value = fields[x].value;
}
socket.emit('api:config.set', { key: key, value: value });
socket.emit('api:config.set', {
key: key,
value: value
});
}
});
}
nodebb_admin.remove = function(key) {
socket.emit('api:config.remove', key);
}
socket.emit('api:config.remove', key);
}
jQuery('document').ready(function() {
@@ -82,7 +82,7 @@ var nodebb_admin = (function(nodebb_admin) {
menuEl.addEventListener('click', function(e) {
parentEl = e.target.parentNode;
if (parentEl.nodeName === 'LI') {
for(var x=0,numLis=liEls.length;x<numLis;x++) {
for (var x = 0, numLis = liEls.length; x < numLis; x++) {
if (liEls[x] !== parentEl) jQuery(liEls[x]).removeClass('active');
else jQuery(parentEl).addClass('active');
}
@@ -93,7 +93,7 @@ var nodebb_admin = (function(nodebb_admin) {
socket.once('api:config.get', function(config) {
nodebb_admin.config = config;
});
socket.emit('api:config.get');
socket.on('api:config.set', function(data) {
@@ -102,7 +102,7 @@ var nodebb_admin = (function(nodebb_admin) {
alert_id: 'config_status',
timeout: 2500,
title: 'Changes Saved',
message: 'Your changes to the NodeBB configuration have been saved. You may have to restart NodeBB to see the changes.',
message: 'Your changes to the NodeBB configuration have been saved.',
type: 'success'
});
} else {
@@ -111,12 +111,11 @@ var nodebb_admin = (function(nodebb_admin) {
timeout: 2500,
title: 'Changes Not Saved',
message: 'NodeBB encountered a problem saving your changes',
type: 'error'
type: 'danger'
});
}
});
return nodebb_admin;
}(nodebb_admin || {}));
return nodebb_admin;
}(nodebb_admin || {}));

View File

@@ -0,0 +1,194 @@
$(document).ready(function() {
var createEl = document.getElementById('create'),
createModal = $('#create-modal'),
createSubmitBtn = document.getElementById('create-modal-go'),
createNameEl = $('#create-group-name'),
detailsModal = $('#group-details-modal'),
detailsSearch = detailsModal.find('#group-details-search'),
searchResults = detailsModal.find('#group-details-search-results'),
groupMembersEl = detailsModal.find('ul.current_members'),
detailsModalSave = detailsModal.find('.btn-primary'),
searchDelay = undefined,
listEl = $('#groups-list');
createEl.addEventListener('click', function() {
createModal.modal('show');
setTimeout(function() {
createNameEl.focus();
}, 250);
}, false);
createSubmitBtn.addEventListener('click', function() {
var submitObj = {
name: createNameEl.val(),
description: $('#create-group-desc').val()
},
errorEl = $('#create-modal-error'),
errorText;
socket.emit('api:groups.create', submitObj, function(err, data) {
if (err) {
switch (err) {
case 'group-exists':
errorText = '<strong>Please choose another name</strong><p>There seems to be a group with this name already.</p>';
break;
case 'name-too-short':
errorText = '<strong>Please specify a grou name</strong><p>A group name is required for administrative purposes.</p>';
break;
default:
errorText = '<strong>Uh-Oh</strong><p>There was a problem creating your group. Please try again later!</p>';
break;
}
errorEl.html(errorText).removeClass('hide');
} else {
createModal.modal('hide');
errorEl.addClass('hide');
createNameEl.val('');
ajaxify.go('admin/groups');
}
});
});
listEl.on('click', 'button[data-action]', function() {
var action = this.getAttribute('data-action'),
gid = $(this).parents('li[data-gid]').attr('data-gid');
switch (action) {
case 'delete':
bootbox.confirm('Are you sure you wish to delete this group?', function(confirm) {
if (confirm) {
socket.emit('api:groups.delete', gid, function(err, data) {
if (data === 'OK') ajaxify.go('admin/groups');
});
}
});
break;
case 'members':
socket.emit('api:groups.get', gid, function(err, groupObj) {
var formEl = detailsModal.find('form'),
nameEl = formEl.find('#change-group-name'),
descEl = formEl.find('#change-group-desc'),
memberIcon = document.createElement('li'),
numMembers = groupObj.members.length,
membersFrag = document.createDocumentFragment(),
memberIconImg, x;
nameEl.val(groupObj.name);
descEl.val(groupObj.description);
// Member list
memberIcon.innerHTML = '<img /><span></span>';
memberIconImg = memberIcon.querySelector('img');
memberIconLabel = memberIcon.querySelector('span');
if (numMembers > 0) {
for (x = 0; x < numMembers; x++) {
memberIconImg.src = groupObj.members[x].picture;
memberIconLabel.innerHTML = groupObj.members[x].username;
memberIcon.setAttribute('data-uid', groupObj.members[x].uid);
membersFrag.appendChild(memberIcon.cloneNode(true));
}
groupMembersEl.html('');
groupMembersEl[0].appendChild(membersFrag);
}
detailsModal.attr('data-gid', groupObj.gid);
detailsModal.modal('show');
});
break;
}
});
detailsSearch.on('keyup', function() {
var searchEl = this;
if (searchDelay) clearTimeout(searchDelay);
searchDelay = setTimeout(function() {
var searchText = searchEl.value,
resultsEl = document.getElementById('group-details-search-results'),
foundUser = document.createElement('li'),
foundUserImg, foundUserLabel;
foundUser.innerHTML = '<img /><span></span>';
foundUserImg = foundUser.getElementsByTagName('img')[0];
foundUserLabel = foundUser.getElementsByTagName('span')[0];
socket.emit('api:admin.user.search', searchText, function(err, results) {
if (!err && results && results.length > 0) {
var numResults = results.length,
resultsSlug = document.createDocumentFragment(),
x;
if (numResults > 4) numResults = 4;
for (x = 0; x < numResults; x++) {
foundUserImg.src = results[x].picture;
foundUserLabel.innerHTML = results[x].username;
foundUser.setAttribute('title', results[x].username);
foundUser.setAttribute('data-uid', results[x].uid);
resultsSlug.appendChild(foundUser.cloneNode(true));
}
resultsEl.innerHTML = '';
resultsEl.appendChild(resultsSlug);
} else resultsEl.innerHTML = '<li>No Users Found</li>';
});
}, 200);
});
searchResults.on('click', 'li[data-uid]', function() {
var userLabel = this,
uid = parseInt(this.getAttribute('data-uid')),
gid = detailsModal.attr('data-gid'),
members = [];
groupMembersEl.find('li[data-uid]').each(function() {
members.push(parseInt(this.getAttribute('data-uid')));
});
if (members.indexOf(uid) === -1) {
socket.emit('api:groups.join', {
gid: gid,
uid: uid
}, function(err, data) {
if (!err) {
groupMembersEl.append(userLabel.cloneNode(true));
}
});
}
});
groupMembersEl.on('click', 'li[data-uid]', function() {
var uid = this.getAttribute('data-uid'),
gid = detailsModal.attr('data-gid');
socket.emit('api:groups.leave', {
gid: gid,
uid: uid
}, function(err, data) {
if (!err) {
groupMembersEl.find('li[data-uid="' + uid + '"]').remove();
}
});
});
detailsModalSave.on('click', function() {
var formEl = detailsModal.find('form'),
nameEl = formEl.find('#change-group-name'),
descEl = formEl.find('#change-group-desc'),
gid = detailsModal.attr('data-gid');
socket.emit('api:groups.update', {
gid: gid,
values: {
name: nameEl.val(),
description: descEl.val()
}
}, function(err) {
if (!err) {
detailsModal.modal('hide');
ajaxify.go('admin/groups');
}
});
});
});

View File

@@ -1,18 +1,18 @@
(function() {
ajaxify.register_events(['api:get_all_rooms']);
socket.on('api:get_all_rooms', function(data) {
var active_users = document.getElementById('active_users'),
total = 0;
active_users.innerHTML = '';
for(var room in data) {
for (var room in data) {
if (room !== '') {
var count = data[room].length;
total += count;
active_users.innerHTML = active_users.innerHTML + "<div class='alert alert-success'><strong>" + room + "</strong> " + count + " active user" + (count > 1 ? "s" : "") + "</div>";
active_users.innerHTML = active_users.innerHTML + "<div class='alert alert-success'><strong>" + room + "</strong> " + count + " active user" + (count > 1 ? "s" : "") + "</div>";
}
}

View File

@@ -1,35 +1,35 @@
var nodebb_admin = nodebb_admin || {};
var nodebb_admin = nodebb_admin || {};
(function() {
var plugins = {
init: function() {
var pluginsList = $('.plugins'),
numPlugins = pluginsList[0].querySelectorAll('li').length,
pluginID, pluginTgl;
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);
});
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';
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>');
}
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: 'warning',
timeout: 5000
})
});
} else {
pluginsList.append('<li><p><i>No plugins found.</i></p></li>');
}
};
}
};
jQuery(document).ready(function() {
nodebb_admin.plugins = plugins;

View File

@@ -1,5 +1,3 @@
var nodebb_admin = (function(nodebb_admin) {
var themes = {};
@@ -10,60 +8,62 @@ var nodebb_admin = (function(nodebb_admin) {
themeContainer = document.querySelector('#bootstrap_themes'),
numThemes = bootswatch.themes.length;
for(var x=0;x<numThemes;x++) {
for (var x = 0; x < numThemes; x++) {
var theme = bootswatch.themes[x];
themeEl.setAttribute('data-css', theme.cssMin);
themeEl.setAttribute('data-theme', theme.name);
themeEl.innerHTML = '<img src="' + theme.thumbnail + '" />' +
'<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>' + theme.name + '</h4>' +
'<p>' + theme.description + '</p>' +
'</div>' +
'<div class="clear">';
themeEl.innerHTML = '<img src="' + theme.thumbnail + '" />' +
'<div>' +
'<div class="pull-right">' +
'<button class="btn btn-primary" data-action="use">Use</button> ' +
'<button class="btn btn-default" data-action="preview">Preview</button>' +
'</div>' +
'<h4>' + theme.name + '</h4>' +
'<p>' + theme.description + '</p>' +
'</div>' +
'<div class="clear">';
themeFrag.appendChild(themeEl.cloneNode(true));
}
themeContainer.innerHTML = '';
themeContainer.appendChild(themeFrag);
}
nodebb_admin.themes = themes;
return nodebb_admin;
}(nodebb_admin || {}));
(function() {
var scriptEl = document.createElement('script');
scriptEl.src = 'http://api.bootswatch.com/2/?callback=nodebb_admin.themes.render';
scriptEl.src = 'http://api.bootswatch.com/3/?callback=nodebb_admin.themes.render';
document.body.appendChild(scriptEl);
var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
var bootstrapThemeContainer = document.querySelector('#bootstrap_themes'),
installedThemeContainer = document.querySelector('#installed_themes'),
themeEvent = function(e) {
if (e.target.hasAttribute('data-action')) {
switch(e.target.getAttribute('data-action')) {
switch (e.target.getAttribute('data-action')) {
case 'preview':
var cssSrc = $(e.target).parents('li').attr('data-css'),
var cssSrc = $(e.target).parents('li').attr('data-css'),
cssEl = document.getElementById('base-theme');
cssEl.href = cssSrc;
break;
break;
case 'use':
var parentEl = $(e.target).parents('li'),
var parentEl = $(e.target).parents('li'),
cssSrc = parentEl.attr('data-css'),
cssName = parentEl.attr('data-theme');
socket.emit('api:config.set', {
key: 'theme:id', value: 'bootswatch:' + cssName
key: 'theme:id',
value: 'bootswatch:' + cssName
});
socket.emit('api:config.set', {
key: 'theme:src', value: cssSrc
key: 'theme:src',
value: cssSrc
});
break;
break;
}
}
};
@@ -82,27 +82,27 @@ var nodebb_admin = (function(nodebb_admin) {
// Installed Themes
socket.emit('api:admin.themes.getInstalled', function(themes) {
var instListEl = document.getElementById('installed_themes'),
var instListEl = document.getElementById('installed_themes'),
themeFrag = document.createDocumentFragment(),
liEl = document.createElement('li');
if (themes.length > 0) {
for(var x=0,numThemes=themes.length;x<numThemes;x++) {
for (var x = 0, numThemes = themes.length; x < numThemes; x++) {
liEl.setAttribute('data-theme', themes[x].id);
liEl.setAttribute('data-css', themes[x].src);
liEl.innerHTML = '<img src="' + themes[x].screenshot + '" />' +
'<div>' +
'<div class="pull-right">' +
'<button class="btn btn-primary" data-action="use">Use</button> ' +
'<button class="btn" data-action="preview">Preview</button>' +
'</div>' +
'<h4>' + themes[x].name + '</h4>' +
'<p>' +
themes[x].description +
(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
'</p>' +
'</div>' +
'<div class="clear">';
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 btn-default" data-action="preview">Preview</button>' +
'</div>' +
'<h4>' + themes[x].name + '</h4>' +
'<p>' +
themes[x].description +
(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
'</p>' +
'</div>' +
'<div class="clear">';
themeFrag.appendChild(liEl.cloneNode(true));
}
} else {

View File

@@ -7,25 +7,37 @@ $(document).ready(function() {
action = this.getAttribute('data-action'),
tid = $this.parents('[data-tid]').attr('data-tid');
switch(action) {
switch (action) {
case 'pin':
if (!$this.hasClass('active')) socket.emit('api:topic.pin', { tid: tid });
else socket.emit('api:topic.unpin', { tid: tid });
break;
if (!$this.hasClass('active')) socket.emit('api:topic.pin', {
tid: tid
});
else socket.emit('api:topic.unpin', {
tid: tid
});
break;
case 'lock':
if (!$this.hasClass('active')) socket.emit('api:topic.lock', { tid: tid });
else socket.emit('api:topic.unlock', { tid: tid });
break;
if (!$this.hasClass('active')) socket.emit('api:topic.lock', {
tid: tid
});
else socket.emit('api:topic.unlock', {
tid: tid
});
break;
case 'delete':
if (!$this.hasClass('active')) socket.emit('api:topic.delete', { tid: tid });
else socket.emit('api:topic.restore', { tid: tid });
break;
if (!$this.hasClass('active')) socket.emit('api:topic.delete', {
tid: tid
});
else socket.emit('api:topic.restore', {
tid: tid
});
break;
}
});
loadMoreEl.addEventListener('click', function() {
if (this.className.indexOf('disabled') === -1) {
var topics = document.querySelectorAll('.topics li[data-tid]'),
var topics = document.querySelectorAll('.topics li[data-tid]'),
lastTid = parseInt(topics[topics.length - 1].getAttribute('data-tid'));
this.innerHTML = '<i class="icon-refresh icon-spin"></i> Retrieving topics';
@@ -37,9 +49,9 @@ $(document).ready(function() {
topics = JSON.parse(topics);
if (topics.length > 0) {
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
topics: topics
}),
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
topics: topics
}),
topicsListEl = document.querySelector('.topics');
topicsListEl.innerHTML += html;
@@ -54,9 +66,9 @@ $(document).ready(function() {
}, false);
// Resolve proper button state for all topics
var topicEls = topicsListEl.querySelectorAll('li'),
var topicEls = topicsListEl.querySelectorAll('li'),
numTopics = topicEls.length;
for(var x=0;x<numTopics;x++) {
for (var x = 0; x < numTopics; x++) {
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';

View File

@@ -1,120 +1,63 @@
(function() {
function initUsers() {
var yourid = templates.get('yourid');
function isUserAdmin(element) {
var parent = $(element).parents('.users-box');
return (parent.attr('data-admin') !== "0");
}
function isUserAdmin(element) {
var parent = $(element).parents('.users-box');
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 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');
}
jQuery('.admin-btn').each(function(index, element) {
var adminBtn = $(element);
var isAdmin = isUserAdmin(adminBtn);
if(isAdmin)
adminBtn.addClass('btn-success');
else
adminBtn.removeClass('btn-success');
});
jQuery('.delete-btn').each(function(index, element) {
var deleteBtn = $(element);
var isAdmin = isUserAdmin(deleteBtn);
if(isAdmin)
deleteBtn.addClass('disabled');
else
deleteBtn.show();
});
function getUID(element) {
var parent = $(element).parents('.users-box');
return parent.attr('data-uid');
}
function updateUserButtons() {
jQuery('.ban-btn').each(function(index, element) {
var banBtn = $(element);
var isAdmin = isUserAdmin(banBtn);
var isBanned = isUserBanned(banBtn);
if(isAdmin)
var uid = getUID(banBtn);
if (isUserAdmin(banBtn) || uid === yourid)
banBtn.addClass('disabled');
else if(isBanned)
else if (isUserBanned(banBtn))
banBtn.addClass('btn-warning');
else
else
banBtn.removeClass('btn-warning');
});
}
jQuery('.admin-btn').on('click', function() {
var adminBtn = $(this);
var isAdmin = isUserAdmin(adminBtn);
var parent = adminBtn.parents('.users-box');
var uid = getUID(adminBtn);
function initUsers() {
if(isAdmin) {
socket.emit('api:admin.user.removeAdmin', uid);
adminBtn.removeClass('btn-success');
parent.find('.delete-btn').removeClass('disabled');
parent.attr('data-admin', 0);
}
else {
bootbox.confirm('Do you really want to make "' + parent.attr('data-username') +'" an admin?', function(confirm) {
if(confirm) {
socket.emit('api:admin.user.makeAdmin', uid);
adminBtn.addClass('btn-success');
parent.find('.delete-btn').addClass('disabled');
parent.attr('data-admin', 1);
}
});
}
return false;
});
updateUserButtons();
jQuery('.delete-btn').on('click', function() {
var deleteBtn = $(this);
var isAdmin = isUserAdmin(deleteBtn);
var parent = deleteBtn.parents('.users-box');
var uid = getUID(deleteBtn);
if(!isAdmin) {
bootbox.confirm('Do you really want to delete "' + parent.attr('data-username') +'"?', function(confirm) {
socket.emit('api:admin.user.deleteUser', uid);
});
}
return false;
});
jQuery('.ban-btn').on('click', function() {
$('#users-container').on('click', '.ban-btn', 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) {
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);
bootbox.confirm('Do you really want to ban "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('api:admin.user.banUser', uid);
banBtn.addClass('btn-warning');
parent.attr('data-banned', 1);
}
});
}
}
return false;
});
}
@@ -122,13 +65,12 @@
jQuery('document').ready(function() {
var yourid = templates.get('yourid'),
timeoutId = 0,
var timeoutId = 0,
loadingMoreUsers = false;
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length-1];
active = parts[parts.length - 1];
jQuery('.nav-pills li').removeClass('active');
jQuery('.nav-pills li a').each(function() {
@@ -138,72 +80,74 @@
}
});
jQuery('#search-user').on('keyup', function () {
if(timeoutId !== 0) {
jQuery('#search-user').on('keyup', function() {
if (timeoutId !== 0) {
clearTimeout(timeoutId);
timeoutId = 0;
}
timeoutId = setTimeout(function() {
var username = $('#search-user').val();
jQuery('.icon-spinner').removeClass('none');
socket.emit('api:admin.user.search', username);
}, 250);
});
initUsers();
socket.removeAllListeners('api:admin.user.search');
socket.on('api:admin.user.search', function(data) {
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
users: data
}),
users: data
}),
userListEl = document.querySelector('.users');
userListEl.innerHTML = html;
jQuery('.icon-spinner').addClass('none');
jQuery('.icon-spinner').addClass('none');
if(data && data.length === 0) {
if (data && data.length === 0) {
$('#user-notfound-notify').html('User not found!')
.show()
.addClass('label-important')
.addClass('label-danger')
.removeClass('label-success');
}
else {
$('#user-notfound-notify').html(data.length + ' user'+(data.length>1?'s':'') + ' found!')
} else {
$('#user-notfound-notify').html(data.length + ' user' + (data.length > 1 ? 's' : '') + ' found!')
.show()
.addClass('label-success')
.removeClass('label-important');
.removeClass('label-danger');
}
initUsers();
});
function onUsersLoaded(users) {
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({ users: users });
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
users: users
});
$('#users-container').append(html);
updateUserButtons();
}
function loadMoreUsers() {
var set = '';
if(active === 'latest') {
if (active === 'latest') {
set = 'users:joindate';
} else if(active === 'sort-posts') {
} else if (active === 'sort-posts') {
set = 'users:postcount';
} else if(active === 'sort-reputation') {
} else if (active === 'sort-reputation') {
set = 'users:reputation';
}
if(set) {
if (set) {
loadingMoreUsers = true;
socket.emit('api:users.loadMore', {
set: set,
after: $('#users-container').children().length
set: set,
after: $('#users-container').children().length
}, function(data) {
if(data.users.length) {
if (data.users.length) {
onUsersLoaded(data.users);
}
loadingMoreUsers = false;

View File

@@ -1,4 +1,4 @@
(function() {
(function () {
var cid = templates.get('category_id'),
room = 'category_' + cid,
twitterEl = document.getElementById('twitter-intent'),
@@ -11,22 +11,22 @@
app.enter_room(room);
twitterEl.addEventListener('click', function() {
twitterEl.addEventListener('click', function () {
window.open(twitter_url, '_blank', 'width=550,height=420,scrollbars=no,status=no');
return false;
}, false);
facebookEl.addEventListener('click', function() {
facebookEl.addEventListener('click', function () {
window.open(facebook_url, '_blank', 'width=626,height=436,scrollbars=no,status=no');
return false;
}, false);
googleEl.addEventListener('click', function() {
googleEl.addEventListener('click', function () {
window.open(google_url, '_blank', 'width=500,height=570,scrollbars=no,status=no');
return false;
}, false);
var new_post = document.getElementById('new_post');
new_post.onclick = function() {
require(['composer'], function(cmp) {
new_post.onclick = function () {
require(['composer'], function (cmp) {
cmp.push(0, cid);
});
}
@@ -36,38 +36,38 @@
]);
function onNewTopic(data) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: [data] }),
topic = document.createElement('div'),
container = document.getElementById('topics-container'),
topics = document.querySelectorAll('#topics-container a'),
numTopics = topics.length,
x;
var html = templates.prepare(templates['category'].blocks['topics']).parse({
topics: [data]
}),
topic = $(html),
container = $('#topics-container'),
topics = $('#topics-container').children(),
numTopics = topics.length;
jQuery('#topics-container, .category-sidebar').removeClass('hidden');
jQuery('#category-no-topics').remove();
topic.innerHTML = html;
topic = topic.querySelector('a');
if (numTopics > 0) {
for(x=0;x<numTopics;x++) {
if (topics[x].querySelector('.icon-pushpin')) continue;
container.insertBefore(topic, topics[x]);
$(topic).hide().fadeIn('slow');
for (var x = 0; x < numTopics; x++) {
if ($(topics[x]).find('.icon-pushpin').length)
continue;
topic.insertBefore(topics[x]);
topic.hide().fadeIn('slow');
break;
}
} else {
container.insertBefore(topic, null);
$(topic).hide().fadeIn('slow');
container.append(topic);
topic.hide().fadeIn('slow');
}
socket.emit('api:categories.getRecentReplies', cid);
$('#topics-container span.timeago').timeago();
}
socket.on('event:new_topic', onNewTopic);
socket.emit('api:categories.getRecentReplies', cid);
socket.on('api:categories.getRecentReplies', function(posts) {
socket.on('api:categories.getRecentReplies', function (posts) {
if (!posts || posts.length === 0) {
return;
}
@@ -76,37 +76,41 @@
recent_replies.innerHTML = '';
var frag = document.createDocumentFragment(),
var frag = document.createDocumentFragment(),
li = document.createElement('li');
for (var i=0,numPosts=posts.length; i<numPosts; i++) {
var dateString = utils.relativeTime(posts[i].timestamp);
for (var i = 0, numPosts = posts.length; i < numPosts; i++) {
li.setAttribute('data-pid', posts[i].pid);
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].topicSlug + '#' + posts[i].pid + '">' +
'<p>' +
'<strong>' + posts[i].username + '</strong>: ' + posts[i].content +
'</p>' +
'<span>posted ' + utils.relativeTime(posts[i].timestamp) + ' ago</span>' +
'</a>';
li.innerHTML = '<a href="/user/' + posts[i].userslug + '"><img title="' + posts[i].username + '" style="width: 48px; height: 48px; /*temporary*/" class="img-rounded" src="' + posts[i].picture + '" class="" /></a>' +
'<a href="/topic/' + posts[i].topicSlug + '#' + posts[i].pid + '">' +
'<p>' +
posts[i].content +
'</p>' +
'<p class="meta"><strong>' + posts[i].username + '</strong></span> -<span class="timeago" title="' + posts[i].relativeTime + '"></span></p>' +
'</a>';
frag.appendChild(li.cloneNode(true));
recent_replies.appendChild(frag);
}
$('#category_recent_replies span.timeago').timeago();
});
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: topics }),
var html = templates.prepare(templates['category'].blocks['topics']).parse({
topics: topics
}),
container = $('#topics-container');
jQuery('#topics-container, .category-sidebar').removeClass('hidden');
jQuery('#category-no-topics').remove();
container.append(html);
}
$('#topics-container span.timeago').timeago();
}
function loadMoreTopics(cid) {
@@ -114,15 +118,15 @@
socket.emit('api:category.loadMore', {
cid: cid,
after: $('#topics-container').children().length
}, function(data) {
if(data.topics.length) {
}, function (data) {
if (data.topics.length) {
onTopicsLoaded(data.topics);
}
loadingMoreTopics = false;
});
}
$(window).off('scroll').on('scroll', function(ev) {
$(window).off('scroll').on('scroll', function (ev) {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {

View File

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

View File

@@ -5,14 +5,14 @@
followersCount = templates.get('followersCount');
$(document).ready(function() {
if(parseInt(followersCount, 10) === 0) {
$('#no-followers-notice').show();
if (parseInt(followersCount, 10) === 0) {
$('#no-followers-notice').removeClass('hide');
}
app.addCommasToNumbers();
});
}());

View File

@@ -5,23 +5,24 @@
followingCount = templates.get('followingCount');
$(document).ready(function() {
if(parseInt(followingCount, 10) === 0) {
$('#no-following-notice').show();
if (parseInt(followingCount, 10) === 0) {
$('#no-following-notice').removeClass('hide');
}
if(yourid !== theirid) {
if (yourid !== theirid) {
$('.unfollow-btn').hide();
}
else {
$('.unfollow-btn').on('click',function() {
} else {
$('.unfollow-btn').on('click', function() {
var unfollowBtn = $(this);
var followingUid = $(this).attr('followingUid');
socket.emit('api:user.unfollow', {uid: followingUid}, function(success) {
socket.emit('api:user.unfollow', {
uid: followingUid
}, function(success) {
var username = unfollowBtn.attr('data-username');
if(success) {
if (success) {
unfollowBtn.parent().remove();
app.alertSuccess('You are no longer following ' + username + '!');
} else {
@@ -34,6 +35,6 @@
app.addCommasToNumbers();
});
}());

View File

@@ -1,91 +1,93 @@
(function() {
var num_users = document.getElementById('number_of_users'),
post_stats = document.getElementById('post_stats'),
latest_user = document.getElementById('latest_user'),
active_users = document.getElementById('active_users'),
user_label = document.getElementById('user_label'),
active_record = document.getElementById('active_record'),
right_menu = document.getElementById('right-menu');
var stats_users = document.getElementById('stats_users'),
stats_topics = document.getElementById('stats_topics'),
stats_posts = document.getElementById('stats_posts'),
stats_online = document.getElementById('stats_online'),
user_label = document.getElementById('user_label');
socket.emit('user.count', {});
socket.on('user.count', function(data) {
num_users.innerHTML = "We currently have <b>" + data.count + "</b> registered users.";
stats_users.innerHTML = data.count;
});
socket.emit('post.stats');
socket.on('post.stats', function(data) {
post_stats.innerHTML = "Our users have created <b>" + data.topics + "</b> topics and made <b>" + data.posts + "</b> posts.";
});
socket.emit('user.latest', {});
socket.on('user.latest', function(data) {
if (data.username == '') {
latest_user.innerHTML = '';
} else {
latest_user.innerHTML = "The most recent user to register is <b><a href='/users/"+data.userslug+"'>" + data.username + "</a></b>.";
}
stats_topics.innerHTML = data.topics;
stats_posts.innerHTML = data.posts;
});
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';
stats_online.innerHTML = data.users;
});
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.emit('api:updateHeader', { fields: ['username', 'picture', 'userslug'] });
socket.on('api:updateHeader', function(data) {
jQuery('#search-button').on('click', function() {
jQuery('#search-fields').removeClass('hide').show();
jQuery(this).hide();
jQuery('#search-fields input').focus()
var rightMenu = $('#right-menu'),
jQuery('#search-form').on('submit', function() {
jQuery('#search-fields').hide();
jQuery('#search-button').show();
});
$('#search-fields input').on('blur', function() {
$('#search-fields').hide();
$('#search-button').show();
});
});
var loggedInMenu = $('#logged-in-menu'),
isLoggedIn = data.uid > 0;
if (isLoggedIn) {
jQuery('.nodebb-loggedin').show();
jQuery('.nodebb-loggedout').hide();
var userLabel = rightMenu.find('#user_label');
if(userLabel.length) {
if(data['userslug'])
userLabel.attr('href','/users/' + data['userslug']);
if(data['picture'])
userLabel.find('img').attr('src',data['picture']);
if(data['username'])
userLabel.find('span').html(data['username']);
} else {
var userli = $('<li> \
<a id="user_label" href="/users/'+data['userslug']+'"> \
<img src="'+data['picture']+'"/> \
<span>'+data['username']+'</span> \
</a> \
</li>');
rightMenu.append(userli);
$('#logged-out-menu').addClass('hide');
$('#logged-in-menu').removeClass('hide');
var logoutli = $('<li><a href="' + RELATIVE_PATH + '/logout">Log out</a></li>');
rightMenu.append(logoutli);
$('#search-button').show();
var userLabel = loggedInMenu.find('#user_label');
if (userLabel.length) {
if (data['userslug'])
userLabel.attr('href', '/user/' + data['userslug']);
if (data['picture'])
userLabel.find('img').attr('src', data['picture']);
if (data['username'])
userLabel.find('span').html(data['username']);
$('#logout-link').on('click', function() {
var csrf_token = $('#csrf_token').val();
$.post(RELATIVE_PATH + '/logout', {
_csrf: csrf_token
}, function() {
window.location = RELATIVE_PATH + '/';
});
});
}
} else {
$('#search-button').hide();
jQuery('.nodebb-loggedin').hide();
jQuery('.nodebb-loggedout').show();
rightMenu.html('');
$('#logged-out-menu').removeClass('hide');
$('#logged-in-menu').addClass('hide');
var registerEl = document.createElement('li'),
loginEl = document.createElement('li');
registerEl.innerHTML = '<a href="/register">Register</a>';
loginEl.innerHTML = '<a href="/login">Login</a>';
right_menu.appendChild(registerEl);
right_menu.appendChild(loginEl);
}
$('#main-nav a,#right-menu a').off('click').on('click', function() {
if($('.navbar .navbar-collapse').hasClass('in'))
$('.navbar-header button').click();
});
});
// Notifications dropdown
@@ -97,20 +99,20 @@
e.preventDefault();
if (notifContainer.className.indexOf('open') === -1) {
socket.emit('api:notifications.get', null, function(data) {
var notifFrag = document.createDocumentFragment(),
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++) {
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++) {
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>';
@@ -132,25 +134,33 @@
});
}
});
notifList.addEventListener('click', function(e) {
var target;
switch(e.target.nodeName) {
case 'SPAN': target = e.target.parentNode.parentNode; break;
case 'A': target = e.target.parentNode; break;
case 'li': target = e.target; break;
switch (e.target.nodeName) {
case 'SPAN':
target = e.target.parentNode.parentNode;
break;
case 'A':
target = e.target.parentNode;
break;
case 'li':
target = e.target;
break;
}
if (target) {
var nid = parseInt(target.getAttribute('data-nid'));
if (nid > 0) socket.emit('api:notifications.mark_read', nid);
}
});
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',
type: 'warning',
timeout: 2000
});
utils.refreshTitle();
@@ -158,20 +168,20 @@
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);
var modal = null;
if (chat.modalExists(data.fromuid)) {
modal = chat.getModal(data.fromuid);
chat.appendChatMessage(modal, data.message, data.timestamp);
} else {
modal = chat.createModal(data.username, data.fromuid);
}
chat.appendChatMessage(chatModal, message);
chat.load(modal.attr('UUID'));
});
});
require(['mobileMenu'], function(mobileMenu) {
mobileMenu.init();
});

View File

@@ -1,13 +1,15 @@
(function() {
// Alternate Logins
var altLoginEl = document.querySelector('.alt-logins');
altLoginEl.addEventListener('click', function(e) {
var target;
switch(e.target.nodeName) {
case 'LI': target = e.target; break;
case 'I': target = e.target.parentNode; break;
switch (e.target.nodeName) {
case 'LI':
target = e.target;
break;
case 'I':
target = e.target.parentNode;
break;
}
if (target) {
document.location.href = target.getAttribute('data-url');
@@ -26,14 +28,19 @@
url: RELATIVE_PATH + '/login',
data: loginData,
success: function(data, textStatus, jqXHR) {
if(!data.success) {
$('#login-error-notify').html(data.message).show();
if (!data.success) {
$('#login-error-notify').show();
} else {
$('#login-error-notify').hide();
window.location.replace(RELATIVE_PATH + "/?loggedin");
if(app.previousUrl.indexOf('/reset/') != -1)
window.location.replace(RELATIVE_PATH + "/?loggedin");
else
window.location.replace(app.previousUrl + "?loggedin");
app.loadConfig();
}
},
error : function(data, textStatus, jqXHR) {
error: function(data, textStatus, jqXHR) {
$('#login-error-notify').show();
},
dataType: 'json',
@@ -44,5 +51,10 @@
return false;
});
$('#login-error-notify button').on('click', function() {
$('#login-error-notify').hide();
return false;
});
document.querySelector('#content input').focus();
}());
}());

View File

@@ -8,32 +8,33 @@
'event:new_post'
]);
var newTopicCount = 0, newPostCount = 0;
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)
if (newTopicCount > 1)
text = 'There are ' + newTopicCount + ' new topics';
else if(newTopicCount === 1)
else if (newTopicCount === 1)
text = 'There is 1 new topic';
else
text = 'There are no new topics';
if(newPostCount > 1)
if (newPostCount > 1)
text += ' and ' + newPostCount + ' new posts.';
else if(newPostCount === 1)
else if (newPostCount === 1)
text += ' and 1 new post.';
else
text += ' and no new posts.';
@@ -42,7 +43,7 @@
$('#new-topics-alert').html(text).fadeIn('slow');
}
socket.on('event:new_post', function(data) {
++newPostCount;
updateAlertText();
@@ -50,7 +51,9 @@
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['recent'].blocks['topics']).parse({ topics: topics }),
var html = templates.prepare(templates['recent'].blocks['topics']).parse({
topics: topics
}),
container = $('#topics-container');
$('#category-no-topics').remove();
@@ -60,8 +63,10 @@
function loadMoreTopics() {
loadingMoreTopics = true;
socket.emit('api:topics.loadMoreRecentTopics', {after:$('#topics-container').children().length}, function(data) {
if(data.topics && data.topics.length) {
socket.emit('api:topics.loadMoreRecentTopics', {
after: $('#topics-container').children().length
}, function(data) {
if (data.topics && data.topics.length) {
onTopicsLoaded(data.topics);
}
loadingMoreTopics = false;

View File

@@ -1,157 +1,154 @@
(function() {
var username = document.getElementById('username'),
password = document.getElementById('password'),
password_confirm = document.getElementById('password-confirm'),
register = document.getElementById('register'),
emailEl = document.getElementById('email'),
username_notify = document.getElementById('username-notify'),
email_notify = document.getElementById('email-notify'),
password_notify = document.getElementById('password-notify'),
password_confirm_notify = document.getElementById('password-confirm-notify'),
usernamevalid = false;
emailexists = false,
emailvalid = false,
userexists = false,
passwordsmatch = false,
passwordvalid = false;
var username = $('#username'),
password = $('#password'),
password_confirm = $('#password-confirm'),
register = $('#register'),
emailEl = $('#email'),
username_notify = $('#username-notify'),
email_notify = $('#email-notify'),
password_notify = $('#password-notify'),
password_confirm_notify = $('#password-confirm-notify'),
validationError = false,
successIcon = '<i class="icon icon-ok"></i>';
$(username).on('keyup change', function() {
usernamevalid = utils.isUserNameValid(username.value);
$('#referrer').val(app.previousUrl);
if(username.value.length < 3) {
username_notify.innerHTML = 'Username too short';
username_notify.className = 'label label-important';
} 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';
function showError(element, msg) {
element.html(msg);
element.parent()
.removeClass('alert-success')
.addClass('alert-danger');
element.show();
validationError = true;
}
function showSuccess(element, msg) {
element.html(msg);
element.parent()
.removeClass('alert-danger')
.addClass('alert-success');
element.show();
}
function validateEmail() {
if (!emailEl.val()) {
validationError = true;
return;
}
if (!utils.isEmailValid(emailEl.val())) {
showError(email_notify, 'Invalid email address.');
} else
socket.emit('user.email.exists', {
email: emailEl.val()
});
}
emailEl.on('blur', function() {
validateEmail();
});
function validateUsername() {
if (!username.val()) {
validationError = true;
return;
}
if (username.val().length < config.minimumUsernameLength) {
showError(username_notify, 'Username too short!');
} else if (username.val().length > config.maximumUsernameLength) {
showError(username_notify, 'Username too long!');
} else if (!utils.isUserNameValid(username.val())) {
showError(username_notify, 'Invalid username!');
} else {
socket.emit('user.exists', {username: username.value});
socket.emit('user.exists', {
username: username.val()
});
}
}
username.on('keyup', function() {
jQuery('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
});
username.on('blur', function() {
validateUsername();
});
$(emailEl).on('keyup change', function() {
emailvalid = utils.isEmailValid(email.value);
function validatePassword() {
if (!password.val()) {
validationError = true;
return;
}
if(!emailvalid) {
email_notify.innerHTML = 'Invalid email address';
email_notify.className = 'label label-important';
if (password.val().length < config.minimumPasswordLength) {
showError(password_notify, 'Password too short!');
} else if (!utils.isPasswordValid(password.val())) {
showError(password_notify, 'Invalid password!');
} else {
showSuccess(password_notify, successIcon);
}
else
socket.emit('user.email.exists', { email: emailEl.value });
if (password.val() !== password_confirm.val() && password_confirm.val() !== '') {
showError(password_confirm_notify, 'Passwords must match!');
}
}
$(password).on('blur', function() {
validatePassword();
});
$(password).on('keyup', function() {
passwordvalid = utils.isPasswordValid(password.value);
if (password.value.length < 6) {
password_notify.innerHTML = 'Password too short';
password_notify.className = 'label label-important';
} else if(!passwordvalid) {
password_notify.innerHTML = 'Invalid password';
password_notify.className = 'label label-important';
} else {
password_notify.innerHTML = 'OK!';
password_notify.className = 'label label-success';
function validatePasswordConfirm() {
if (!password.val() || password_notify.hasClass('alert-error')) {
return;
}
if(password.value !== password_confirm.value) {
password_confirm_notify.innerHTML = 'Passwords must match!';
password_confirm_notify.className = 'label label-important';
passwordsmatch = false;
}
});
$(password_confirm).on('keyup', function() {
if(password.value !== password_confirm.value) {
password_confirm_notify.innerHTML = 'Passwords must match!';
password_confirm_notify.className = 'label label-important';
passwordsmatch = false;
}
else {
password_confirm_notify.innerHTML = 'OK!';
password_confirm_notify.className = 'label label-success';
passwordsmatch = true;
if (password.val() !== password_confirm.val()) {
showError(password_confirm_notify, 'Passwords must match!');
} else {
showSuccess(password_confirm_notify, successIcon);
}
}
$(password_confirm).on('blur', function() {
validatePasswordConfirm();
});
ajaxify.register_events(['user.exists', 'user.email.exists']);
socket.on('user.exists', function(data) {
userexists = data.exists;
if (data.exists === true) {
username_notify.innerHTML = 'Username exists';
username_notify.className = 'label label-important';
showError(username_notify, 'Username already taken!');
} else {
username_notify.innerHTML = 'OK!';
username_notify.className = 'label label-success';
showSuccess(username_notify, successIcon);
}
});
socket.on('user.email.exists', function(data) {
emailexists = data.exists;
socket.on('user.email.exists', function(data) {
if (data.exists === true) {
email_notify.innerHTML = 'Email Address exists';
email_notify.className = 'label label-important';
}
else {
email_notify.innerHTML = 'OK!';
email_notify.className = 'label label-success';
showError(email_notify, 'Email address already taken!');
} else {
showSuccess(email_notify, successIcon);
}
});
// Alternate Logins
var altLoginEl = document.querySelector('.alt-logins');
altLoginEl.addEventListener('click', function(e) {
var target;
switch(e.target.nodeName) {
case 'LI': target = e.target; break;
case 'I': target = e.target.parentNode; break;
}
if (target) {
document.location.href = target.getAttribute('data-url');
}
$('.alt-logins li').on('click', function(e) {
document.location.href = $(this).attr('data-url');
});
function validateForm() {
var validated = true;
if (username.value.length < 2 || !usernamevalid) {
username_notify.innerHTML = 'Invalid username';
username_notify.className = 'label label-important';
validated = false;
}
validationError = false;
if (password.value.length < 5) {
password_notify.innerHTML = 'Password too short';
validated = false;
}
if(password.value !== password_confirm.value) {
password_confirm_notify.innerHTML = 'Passwords must match!';
}
validateEmail();
validateUsername();
validatePassword();
validatePasswordConfirm();
if (!emailvalid) {
email_notify.innerHTML = 'Invalid email address';
validated = false;
}
if(emailexists) {
email_notify.innerHTML = 'Email Address exists';
validated = false;
}
if(userexists || !passwordsmatch || !passwordvalid)
validated = false;
return validated;
return validationError;
}
register.addEventListener('click', function(e) {
if (!validateForm()) e.preventDefault();
}, false);
}());
register.on('click', function(e) {
if (validateForm()) e.preventDefault();
});
}());

View File

@@ -1,11 +1,13 @@
(function() {
var inputEl = document.getElementById('email'),
var inputEl = document.getElementById('email'),
errorEl = document.getElementById('error'),
errorTextEl = errorEl.querySelector('p');
document.getElementById('reset').onclick = function() {
if (inputEl.value.length > 0 && inputEl.value.indexOf('@') !== -1) {
socket.emit('user:reset.send', { email: inputEl.value });
socket.emit('user:reset.send', {
email: inputEl.value
});
} else {
jQuery('#success').hide();
jQuery(errorEl).show();
@@ -14,9 +16,9 @@
};
ajaxify.register_events(['user.send_reset']);
socket.on('user.send_reset', function(data) {
var submitEl = document.getElementById('reset');
var submitEl = document.getElementById('reset');
if (data.status === 'ok') {
jQuery('#error').hide();
@@ -26,13 +28,13 @@
} else {
jQuery('#success').hide();
jQuery(errorEl).show();
switch(data.message) {
switch (data.message) {
case 'invalid-email':
errorTextEl.innerHTML = 'The email you put in (<span>' + data.email + '</span>) is not registered with us. Please try again.';
break;
break;
case 'send-failed':
errorTextEl.innerHTML = 'There was a problem sending the reset code. Please try again later.';
break;
break;
}
}
});

View File

@@ -1,7 +1,7 @@
(function() {
var reset_code = templates.get('reset_code');
var resetEl = document.getElementById('reset'),
var resetEl = document.getElementById('reset'),
password = document.getElementById('password'),
repeat = document.getElementById('repeat'),
noticeEl = document.getElementById('notice');
@@ -10,20 +10,30 @@
if (password.value.length < 6) {
$('#error').hide();
noticeEl.querySelector('strong').innerHTML = 'Invalid Password';
noticeEl.querySelector('p').innerHTML = 'The password entered it too short, please pick a different password!';
noticeEl.querySelector('p').innerHTML = 'The password entered is too short, please pick a different password.';
noticeEl.style.display = 'block';
} else if (password.value === repeat.value) {
socket.emit('user:reset.commit', { code: reset_code, password: password.value });
} else if (password.value !== repeat.value) {
$('#error').hide();
noticeEl.querySelector('strong').innerHTML = 'Invalid Password';
noticeEl.querySelector('p').innerHTML = 'The two passwords you\'ve entered do not match.';
noticeEl.style.display = 'block';
} else {
socket.emit('user:reset.commit', {
code: reset_code,
password: password.value
});
}
}, false);
// Enable the form if the code is valid
socket.emit('user:reset.valid', { code: reset_code });
socket.emit('user:reset.valid', {
code: reset_code
});
ajaxify.register_events(['user:reset.valid', 'user:reset.commit']);
socket.on('user:reset.valid', function(data) {
if (!!data.valid) resetEl.disabled = false;
if ( !! data.valid) resetEl.disabled = false;
else {
var formEl = document.getElementById('reset-form');
// Show error message

View File

@@ -2,13 +2,23 @@
$(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>');
text = text.replace(regex, '<span class="label label-success">' + searchQuery + '</span>');
$(this).html(text);
});
$('#search-form input').val(searchQuery);
$('#mobile-search-form').off('submit').on('submit', function() {
var input = $(this).find('input');
ajaxify.go("search/" + input.val(), null, "search");
input.val('');
return false;
});
});
})();

View File

@@ -1,5 +1,5 @@
(function() {
var expose_tools = templates.get('expose_tools'),
var expose_tools = templates.get('expose_tools'),
tid = templates.get('topic_id'),
postListEl = document.getElementById('post-container'),
editBtns = document.querySelectorAll('#post-container .post-buttons .edit, #post-container .post-buttons .edit i'),
@@ -15,7 +15,7 @@
app.addCommasToNumbers();
var room = 'topic_' + tid,
var room = 'topic_' + tid,
adminTools = document.getElementById('thread-tools');
app.enter_room(room);
@@ -34,11 +34,15 @@
$('#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 });
if (confirm) socket.emit('api:topic.delete', {
tid: tid
});
});
} else {
bootbox.confirm('Are you sure you want to restore this thread?', function(confirm) {
if (confirm) socket.emit('api:topic.restore', { tid: tid });
if (confirm) socket.emit('api:topic.restore', {
tid: tid
});
});
}
return false;
@@ -46,18 +50,26 @@
$('#lock_thread').on('click', function(e) {
if (thread_state.locked !== '1') {
socket.emit('api:topic.lock', { tid: tid });
socket.emit('api:topic.lock', {
tid: tid
});
} else {
socket.emit('api:topic.unlock', { tid: tid });
socket.emit('api:topic.unlock', {
tid: tid
});
}
return false;
});
$('#pin_thread').on('click', function(e) {
if (thread_state.pinned !== '1') {
socket.emit('api:topic.pin', { tid: tid });
socket.emit('api:topic.pin', {
tid: tid
});
} else {
socket.emit('api:topic.unpin', { tid: tid });
socket.emit('api:topic.unpin', {
tid: tid
});
}
return false;
});
@@ -67,12 +79,13 @@
return false;
});
moveThreadModal.on('shown', function() {
moveThreadModal.on('shown.bs.modal', function() {
var loadingEl = document.getElementById('categories-loading');
if (loadingEl) {
socket.once('api:categories.get', function(data) {
// Render categories
var categoriesFrag = document.createDocumentFragment(),
var categoriesFrag = document.createDocumentFragment(),
categoryEl = document.createElement('li'),
numCategories = data.categories.length,
modalBody = moveThreadModal.find('.modal-body'),
@@ -84,7 +97,7 @@
x, info, targetCid, targetCatLabel;
categoriesEl.className = 'category-list';
for(x=0;x<numCategories;x++) {
for (x = 0; x < numCategories; x++) {
info = data.categories[x];
categoryEl.className = info.blockclass;
categoryEl.innerHTML = '<i class="' + info.icon + '"></i> ' + info.name;
@@ -124,14 +137,17 @@
} else {
app.alert({
'alert_id': 'thread_move',
type: 'error',
type: 'danger',
title: 'Unable to Move Topic',
message: 'This topic could not be moved to ' + targetCatLabel + '.<br />Please try again later',
timeout: 5000
});
}
});
socket.emit('api:topic.move', { tid: tid, cid: targetCid });
socket.emit('api:topic.move', {
tid: tid,
cid: targetCid
});
}
});
});
@@ -141,8 +157,8 @@
}
// Fix delete state for this thread's posts
var postEls = document.querySelectorAll('#post-container li[data-deleted]');
for(var x=0,numPosts=postEls.length;x<numPosts;x++) {
var postEls = document.querySelectorAll('#post-container li[data-deleted]');
for (var x = 0, numPosts = postEls.length; x < numPosts; x++) {
if (postEls[x].getAttribute('data-deleted') === '1') toggle_post_delete_state(postEls[x].getAttribute('data-pid'));
postEls[x].removeAttribute('data-deleted');
}
@@ -183,7 +199,7 @@
if (data.status && data.status === 'ok') set_follow_state(data.follow);
else {
app.alert({
type: 'error',
type: 'danger',
alert_id: 'topic_follow',
title: 'Please Log In',
message: 'Please register or log in in order to subscribe to this topic',
@@ -193,12 +209,26 @@
});
socket.emit('api:topic.followCheck', tid);
if(followEl[0]) {
if (followEl[0]) {
followEl[0].addEventListener('click', function() {
socket.emit('api:topic.follow', tid);
}, false);
}
enableInfiniteLoading();
var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
if(bookmark) {
app.scrollToPost(parseInt(bookmark, 10));
}
$('#post-container').on('click', '.deleted', function(ev) {
$(this).toggleClass('deleted-expanded');
});
});
function enableInfiniteLoading() {
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
@@ -206,31 +236,27 @@
app.loadMorePosts(tid);
}
});
$('.post-container').on('click', '.deleted', function(ev) {
$(this).toggleClass('deleted-expanded');
});
});
}
var reply_fn = function() {
var selectionText = '',
selection = window.getSelection() || document.getSelection();
var selectionText = '',
selection = window.getSelection() || document.getSelection();
if ($(selection.baseNode).parents('.post-content').length > 0) {
var snippet = selection.toString();
if (snippet.length > 0) selectionText = '> ' + snippet.replace(/\n/g, '\n> ');
}
if ($(selection.baseNode).parents('.post-content').length > 0) {
var snippet = selection.toString();
if (snippet.length > 0) selectionText = '> ' + snippet.replace(/\n/g, '\n> ');
}
if (thread_state.locked !== '1') {
require(['composer'], function(cmp) {
cmp.push(tid, null, null, selectionText.length > 0 ? selectionText + '\n\n' : '');
});
}
};
$('.post-container').on('click', '.post_reply', reply_fn);
if (thread_state.locked !== '1') {
require(['composer'], function(cmp) {
cmp.push(tid, null, null, selectionText.length > 0 ? selectionText + '\n\n' : '');
});
}
};
$('#post-container').on('click', '.post_reply', reply_fn);
$('#post_reply').on('click', reply_fn);
$('.post-container').on('click', '.quote', function() {
$('#post-container').on('click', '.quote', function() {
if (thread_state.locked !== '1') {
var pid = $(this).parents('li').attr('data-pid');
@@ -241,60 +267,72 @@
cmp.push(tid, null, null, quoted);
});
});
socket.emit('api:posts.getRawPost', { pid: pid });
socket.emit('api:posts.getRawPost', {
pid: pid
});
}
});
$('.post-container').on('click', '.favourite', function() {
var ids = this.id.replace('favs_', '').split('_'),
pid = ids[0],
uid = ids[1];
$('#post-container').on('click', '.favourite', function() {
var pid = $(this).parents('li').attr('data-pid');
var uid = $(this).parents('li').attr('data-uid');
var element = $(this).find('i');
if(element.attr('class') == 'icon-star-empty') {
socket.emit('api:posts.favourite', {pid: pid, room_id: app.current_room});
}
else {
socket.emit('api:posts.unfavourite', {pid: pid, room_id: app.current_room});
if (element.attr('class') == 'icon-star-empty') {
socket.emit('api:posts.favourite', {
pid: pid,
room_id: app.current_room
});
} else {
socket.emit('api:posts.unfavourite', {
pid: pid,
room_id: app.current_room
});
}
});
$('.post-container').delegate('.edit', 'click', function(e) {
var pid = ($(this).attr('id') || $(this.parentNode).attr('id')).split('_')[1];
$('#post-container').on('click', '.link', function() {
var pid = $(this).parents('li').attr('data-pid');
$('#post_' + pid + '_link').val(window.location.href + "#" + pid).stop(true, false).fadeIn().select();
$('#post_' + pid + '_link').off('blur').on('blur', function() {
$(this).fadeOut();
});
});
var main = $(this).parents('.main-post');
$('#post-container').delegate('.edit', 'click', function(e) {
var pid = $(this).parents('li').attr('data-pid'),
main = $(this).parents('.main-post');
require(['composer'], function(cmp) {
cmp.push(null, null, pid);
});
});
$('.post-container').delegate('.delete', 'click', function(e) {
var pid = ($(this).attr('id') || $(this.parentNode).attr('id')).split('_')[1],
postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
deleteAction = !postEl.hasClass('deleted') ? true : false,
confirmDel = confirm((deleteAction ? 'Delete' : 'Restore') + ' this post?');
$('#post-container').delegate('.delete', 'click', function(e) {
var pid = $(this).parents('li').attr('data-pid'),
postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
deleteAction = !postEl.hasClass('deleted') ? true : false,
confirmDel = confirm((deleteAction ? 'Delete' : 'Restore') + ' this post?');
if (confirmDel) {
deleteAction ?
socket.emit('api:posts.delete', { pid: pid }) :
socket.emit('api:posts.restore', { pid: pid });
socket.emit('api:posts.delete', {
pid: pid
}) :
socket.emit('api:posts.restore', {
pid: pid
});
}
});
$('.post-container').delegate('.chat', 'click', function(e) {
$('#post-container').on('click', '.chat', function(e) {
var username = $(this).parents('li.row').attr('data-username');
var touid = $(this).parents('li.row').attr('data-uid');
var username = $(this).parents('li').attr('data-username');
var touid = $(this).parents('li').attr('data-uid');
if(username === app.username || !app.username)
if (username === app.username || !app.username)
return;
require(['chat'], function(chat) {
var chatModal = chat.createModalIfDoesntExist(username, touid);
chatModal.show();
chat.bringModalToTop(chatModal);
});
app.openChat(username, touid);
});
ajaxify.register_events([
@@ -308,7 +346,7 @@
socket.on('api:get_users_in_room', function(data) {
var activeEl = $('#thread_active_users');
if(activeEl.length)
if (activeEl.length)
activeEl.html(data);
app.populate_online_users();
@@ -369,9 +407,9 @@
socket.on('event:post_edited', function(data) {
var editedPostEl = document.getElementById('content_' + data.pid);
var editedPostTitle = $('#topic_title_'+data.pid);
var editedPostTitle = $('#topic_title_' + data.pid);
if(editedPostTitle.length > 0) {
if (editedPostTitle.length > 0) {
editedPostTitle.fadeOut(250, function() {
editedPostTitle.html(data.title);
editedPostTitle.fadeIn(250);
@@ -382,6 +420,7 @@
this.innerHTML = data.content;
$(this).fadeIn(250);
});
});
socket.on('api:posts.favourite', function(data) {
@@ -426,12 +465,12 @@
ptotal += value;
utotal += value;
post_rep.html(ptotal+ ' ');
user_rep.html(utotal+ ' ');
post_rep.html(ptotal + ' ');
user_rep.html(utotal + ' ');
}
function set_locked_state(locked, alert) {
var threadReplyBtn = document.getElementById('post_reply'),
var threadReplyBtn = document.getElementById('post_reply'),
postReplyBtns = document.querySelectorAll('#post-container .post_reply'),
quoteBtns = document.querySelectorAll('#post-container .quote'),
editBtns = document.querySelectorAll('#post-container .edit'),
@@ -444,7 +483,7 @@
lockThreadEl.innerHTML = '<i class="icon-unlock"></i> Unlock Thread';
threadReplyBtn.disabled = true;
threadReplyBtn.innerHTML = 'Locked <i class="icon-lock"></i>';
for(x=0;x<numPosts;x++) {
for (x = 0; x < numPosts; x++) {
postReplyBtns[x].innerHTML = 'Locked <i class="icon-lock"></i>';
quoteBtns[x].style.display = 'none';
editBtns[x].style.display = 'none';
@@ -466,7 +505,7 @@
lockThreadEl.innerHTML = '<i class="icon-lock"></i> Lock Thread';
threadReplyBtn.disabled = false;
threadReplyBtn.innerHTML = 'Reply';
for(x=0;x<numPosts;x++) {
for (x = 0; x < numPosts; x++) {
postReplyBtns[x].innerHTML = 'Reply <i class="icon-reply"></i>';
quoteBtns[x].style.display = 'inline-block';
editBtns[x].style.display = 'inline-block';
@@ -488,25 +527,25 @@
}
function set_delete_state(deleted) {
var deleteThreadEl = document.getElementById('delete_thread'),
var deleteThreadEl = document.getElementById('delete_thread'),
deleteTextEl = deleteThreadEl.getElementsByTagName('span')[0],
threadEl = document.querySelector('.post-container'),
threadEl = $('#post-container'),
deleteNotice = document.getElementById('thread-deleted') || document.createElement('div');
if (deleted) {
deleteTextEl.innerHTML = '<i class="icon-comment"></i> Restore Thread';
$(threadEl).addClass('deleted');
threadEl.addClass('deleted');
// Spawn a 'deleted' notice at the top of the page
deleteNotice.setAttribute('id', 'thread-deleted');
deleteNotice.className = 'alert';
deleteNotice.className = 'alert alert-warning';
deleteNotice.innerHTML = 'This thread has been deleted. Only users with thread management privileges can see it.';
document.getElementById('content').insertBefore(deleteNotice, threadEl);
threadEl.before(deleteNotice);
thread_state.deleted = '1';
} else {
deleteTextEl.innerHTML = '<i class="icon-trash"></i> Delete Thread';
$(threadEl).removeClass('deleted');
threadEl.removeClass('deleted');
deleteNotice.parentNode.removeChild(deleteNotice);
thread_state.deleted = '0';
@@ -546,7 +585,7 @@
}
function toggle_post_delete_state(pid) {
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]'));
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]'));
if (postEl[0]) {
quoteEl = $(postEl[0].querySelector('.quote')),
@@ -573,7 +612,7 @@
}
function toggle_post_tools(pid, state) {
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
quoteEl = $(postEl[0].querySelector('.quote')),
favEl = $(postEl[0].querySelector('.favourite')),
replyEl = $(postEl[0].querySelector('.post_reply'));
@@ -590,7 +629,7 @@
}
function toggle_mod_tools(pid, state) {
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
editEl = postEl.find('.edit'),
deleteEl = postEl.find('.delete');
@@ -602,4 +641,76 @@
deleteEl.addClass('none');
}
}
})();
var postAuthorImage, mobileAuthorOverlay, pagination;
var postcount = templates.get('postcount');
function updateHeader() {
if (pagination == null) {
jQuery('.pagination-block i:first').on('click', function() {
app.scrollToTop();
});
jQuery('.pagination-block i:last').on('click', function() {
app.scrollToBottom();
});
}
jQuery('.mobile-author-overlay').css('bottom', '0px');
postAuthorImage = postAuthorImage || document.getElementById('mobile-author-image');
mobileAuthorOverlay = mobileAuthorOverlay || document.getElementById('mobile-author-overlay');
pagination = pagination || document.getElementById('pagination');
pagination.parentNode.style.display = 'block';
var windowHeight = jQuery(window).height();
var scrollTop = jQuery(window).scrollTop();
var scrollBottom = scrollTop + windowHeight;
if (scrollTop < 50 && postcount > 1) {
localStorage.removeItem("topic:" + tid + ":bookmark");
postAuthorImage.src = (jQuery('.main-post .avatar img').attr('src'));
mobileAuthorOverlay.innerHTML = 'Posted by ' + jQuery('.main-post').attr('data-username') + ', ' + jQuery('.main-post').find('.relativeTimeAgo').html();
pagination.innerHTML = '0 out of ' + postcount;
return;
}
var count = 0, smallestNonNegative = 0;
jQuery('.sub-posts').each(function() {
count++;
this.postnumber = count;
var el = jQuery(this);
var elTop = el.offset().top;
var height = Math.floor(el.height());
var elBottom = elTop + (height < 300 ? height : 300);
var inView = ((elBottom >= scrollTop) && (elTop <= scrollBottom) && (elBottom <= scrollBottom) && (elTop >= scrollTop));
if (inView) {
if(elTop - scrollTop > smallestNonNegative) {
localStorage.setItem("topic:" + tid + ":bookmark", el.attr('data-pid'));
smallestNonNegative = Number.MAX_VALUE;
}
pagination.innerHTML = this.postnumber + ' out of ' + postcount;
postAuthorImage.src = (jQuery(this).find('.profile-image-block img').attr('src'));
mobileAuthorOverlay.innerHTML = 'Posted by ' + jQuery(this).attr('data-username') + ', ' + jQuery(this).find('.relativeTimeAgo').html();
}
});
setTimeout(function() {
if (scrollTop + windowHeight == jQuery(document).height()) {
pagination.innerHTML = postcount + ' out of ' + postcount;
}
}, 100);
}
window.onscroll = updateHeader;
window.onload = updateHeader;
})();

View File

@@ -8,32 +8,33 @@
'event:new_post'
]);
var newTopicCount = 0, newPostCount = 0;
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)
if (newTopicCount > 1)
text = 'There are ' + newTopicCount + ' new topics';
else if(newTopicCount === 1)
else if (newTopicCount === 1)
text = 'There is 1 new topic';
else
text = 'There are no new topics';
if(newPostCount > 1)
if (newPostCount > 1)
text += ' and ' + newPostCount + ' new posts.';
else if(newPostCount === 1)
else if (newPostCount === 1)
text += ' and 1 new post.';
else
text += ' and no new posts.';
@@ -42,19 +43,19 @@
$('#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) {
socket.emit('api:topics.markAllRead', {}, function(success) {
if (success) {
btn.remove();
$('#topics-container').empty();
$('#category-no-topics').removeClass('hidden');
$('#category-no-topics').removeClass('hidden');
app.alertSuccess('All topics marked as read!');
$('#numUnreadBadge')
.removeClass('badge-important')
@@ -68,7 +69,9 @@
function onTopicsLoaded(topics) {
var html = templates.prepare(templates['unread'].blocks['topics']).parse({ topics: topics }),
var html = templates.prepare(templates['unread'].blocks['topics']).parse({
topics: topics
}),
container = $('#topics-container');
$('#category-no-topics').remove();
@@ -78,12 +81,14 @@
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) {
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();
$('#load-more-btn').hide();
}
loadingMoreTopics = false;
@@ -92,14 +97,14 @@
$(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)
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20)
$('#load-more-btn').show();
$('#load-more-btn').on('click', function() {

View File

@@ -1,12 +1,14 @@
(function() {
$(document).ready(function() {
var timeoutId = 0;
var loadingMoreUsers = false;
var url = window.location.href,
parts = url.split('/'),
active = parts[parts.length-1];
active = parts[parts.length - 1];
var lastSearch = null;
app.addCommasToNumbers();
@@ -17,93 +19,111 @@
return false;
}
});
jQuery('#search-user').on('keyup', function () {
if(timeoutId !== 0) {
jQuery('#search-user').on('keyup', function() {
if (timeoutId !== 0) {
clearTimeout(timeoutId);
timeoutId = 0;
}
timeoutId = setTimeout(function() {
var username = $('#search-user').val();
jQuery('.icon-spinner').removeClass('none');
socket.emit('api:admin.user.search', username);
if (username == '') {
jQuery('#user-notfound-notify').html('<i class="icon icon-circle-blank"></i>');
jQuery('#user-notfound-notify').parent().removeClass('btn-warning label-warning btn-success label-success');
return;
}
if (lastSearch === username) return;
lastSearch = username;
jQuery('#user-notfound-notify').html('<i class="icon-spinner icon-spin"></i>');
setTimeout(function() {
socket.emit('api:admin.user.search', username);
}, 500); //replace this with global throttling function/constant
}, 250);
});
socket.removeAllListeners('api:admin.user.search');
socket.on('api:admin.user.search', function(data) {
jQuery('.icon-spinner').addClass('none');
if(data === null) {
$('#user-notfound-notify').html('You need to be logged in to search!')
.show()
.addClass('label-important')
.removeClass('label-success');
if (data === null) {
$('#user-notfound-notify').html('You need to be logged in to search!');
$('#user-notfound-notify').parent().addClass('btn-warning label-warning');
return;
}
var html = templates.prepare(templates['users'].blocks['users']).parse({
users: data
}),
userListEl = document.querySelector('.users');
users: data
}),
userListEl = document.querySelector('#users-container');
userListEl.innerHTML = html;
if(data && data.length === 0) {
$('#user-notfound-notify').html('User not found!')
.show()
.addClass('label-important')
.removeClass('label-success');
}
else {
$('#user-notfound-notify').html(data.length + ' user'+(data.length>1?'s':'') + ' found!')
.show()
.addClass('label-success')
.removeClass('label-important');
if (data && data.length === 0) {
$('#user-notfound-notify').html('User not found!');
$('#user-notfound-notify').parent().addClass('btn-warning label-warning');
} else {
$('#user-notfound-notify').html(data.length + ' user' + (data.length > 1 ? 's' : '') + ' found!');
$('#user-notfound-notify').parent().addClass('btn-success label-success');
}
});
socket.on('api:user.isOnline', function(data) {
if(active == 'online' && !loadingMoreUsers) {
$('#users-container').empty();
startLoading('users:online', 0);
}
});
function onUsersLoaded(users) {
var html = templates.prepare(templates['users'].blocks['users']).parse({ users: users });
var html = templates.prepare(templates['users'].blocks['users']).parse({
users: users
});
$('#users-container').append(html);
}
function loadMoreUsers() {
var set = '';
if(active === 'users-latest' || active === 'users') {
if (active === 'latest' || active === 'users') {
set = 'users:joindate';
} else if(active === 'users-sort-posts') {
} else if (active === 'sort-posts') {
set = 'users:postcount';
} else if(active === 'users-sort-reputation') {
set = 'users:reputation';
} else if (active === 'sort-reputation') {
set = 'users:reputation';
} else if (active === 'online') {
set = 'users:online';
}
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;
});
if (set) {
startLoading(set, $('#users-container').children().length);
}
}
function startLoading(set, after) {
loadingMoreUsers = true;
socket.emit('api:users.loadMore', {
set: set,
after: after
}, function(data) {
if (data.users.length) {
onUsersLoaded(data.users);
$('#load-more-users-btn').removeClass('disabled');
} 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;

View File

@@ -13,37 +13,75 @@ define(['taskbar'], function(taskbar) {
chatModal.css('zIndex', topZ + 1);
}
module.createModalIfDoesntExist = function(username, touid) {
var chatModal = $('#chat-modal-'+touid);
module.getModal = function(touid) {
return $('#chat-modal-' + touid);
}
if(!chatModal.length) {
var chatModal = $('#chat-modal').clone();
chatModal.attr('id','chat-modal-'+touid);
var uuid = utils.generateUUID();
chatModal.attr('UUID', uuid);
chatModal.appendTo($('body'));
chatModal.draggable({
start:function(){
module.bringModalToTop(chatModal);
module.modalExists = function(touid) {
return $('#chat-modal-' + touid).length !== 0;
}
function checkStatus(chatModal, callback) {
socket.emit('api:user.isOnline', chatModal.touid, function(data) {
if(data.online !== chatModal.online) {
if(data.online) {
module.appendChatMessage(chatModal, chatModal.username + ' is currently online.\n', data.timestamp);
} else {
module.appendChatMessage(chatModal, chatModal.username + ' is currently offline.\n', data.timestamp);
}
});
chatModal.find('#chat-with-name').html(username);
chatModal.find('.close').on('click',function(e){
chatModal.hide();
taskbar.discard('chat', uuid);
});
chatModal.on('click', function(e) {
module.bringModalToTop(chatModal);
});
addSendHandler(chatModal, touid);
getChatMessages(chatModal, touid);
chatModal.online = data.online;
}
if(callback)
callback(data.online);
});
}
function checkOnlineStatus(chatModal) {
if(chatModal.intervalId === 0) {
chatModal.intervalId = setInterval(function() {
checkStatus(chatModal);
}, 1000);
}
}
module.createModal = function(username, touid, callback) {
var chatModal = $('#chat-modal').clone(),
uuid = utils.generateUUID();
chatModal.intervalId = 0;
chatModal.touid = touid;
chatModal.username = username;
taskbar.push('chat', chatModal.attr('UUID'), {title:'chat with '+username});
chatModal.attr('id', 'chat-modal-' + touid);
chatModal.attr('UUID', uuid);
chatModal.appendTo($('body'));
chatModal.draggable({
start:function() {
module.bringModalToTop(chatModal);
}
});
chatModal.find('#chat-with-name').html(username);
chatModal.find('.close').on('click', function(e) {
clearInterval(chatModal.intervalId);
chatModal.intervalId = 0;
chatModal.hide();
taskbar.discard('chat', uuid);
});
chatModal.on('click', function(e) {
module.bringModalToTop(chatModal);
});
addSendHandler(chatModal);
getChatMessages(chatModal, function() {
checkOnlineStatus(chatModal);
});
taskbar.push('chat', chatModal.attr('UUID'), {title:'chat with ' + username});
return chatModal;
}
@@ -51,51 +89,56 @@ define(['taskbar'], function(taskbar) {
var chatModal = $('div[UUID="'+uuid+'"]');
chatModal.show();
module.bringModalToTop(chatModal);
checkOnlineStatus(chatModal);
}
module.minimize = function(uuid) {
var chatModal = $('div[UUID="'+uuid+'"]');
chatModal.hide();
taskbar.minimize('chat', uuid);
clearInterval(chatModal.intervalId);
chatModal.intervalId = 0;
}
function getChatMessages(chatModal, touid) {
socket.emit('getChatMessages', {touid:touid}, function(messages) {
function getChatMessages(chatModal, callback) {
socket.emit('getChatMessages', {touid:chatModal.touid}, function(messages) {
for(var i = 0; i<messages.length; ++i) {
module.appendChatMessage(chatModal, messages[i].content);
module.appendChatMessage(chatModal, messages[i].content, messages[i].timestamp);
}
callback();
});
}
function addSendHandler(chatModal, touid) {
function addSendHandler(chatModal) {
chatModal.find('#chat-message-input').off('keypress');
chatModal.find('#chat-message-input').on('keypress', function(e) {
if(e.which === 13) {
sendMessage(chatModal, touid);
sendMessage(chatModal);
}
});
chatModal.find('#chat-message-send-btn').off('click');
chatModal.find('#chat-message-send-btn').on('click', function(e){
sendMessage(chatModal, touid);
sendMessage(chatModal);
return false;
});
}
function sendMessage(chatModal, touid) {
function sendMessage(chatModal) {
var msg = app.strip_tags(chatModal.find('#chat-message-input').val());
if(msg.length) {
msg = msg +'\n';
socket.emit('sendChatMessage', { touid:touid, message:msg});
socket.emit('sendChatMessage', { touid:chatModal.touid, message:msg});
chatModal.find('#chat-message-input').val('');
module.appendChatMessage(chatModal, 'You : ' + msg);
}
}
module.appendChatMessage = function(chatModal, message){
module.appendChatMessage = function(chatModal, message, timestamp) {
var chatContent = chatModal.find('#chat-content');
chatContent.append(message);
var date = new Date(parseInt(timestamp, 10));
chatContent.append('[' + date.toLocaleTimeString() + '] ' + message);
chatContent.scrollTop(
chatContent[0].scrollHeight - chatContent.height()
);

View File

@@ -1,38 +1,42 @@
define(['taskbar'], function(taskbar) {
var composer = {
initialized: false,
active: undefined,
taskbar: taskbar,
posts: {},
postContainer: undefined,
};
initialized: false,
active: undefined,
taskbar: taskbar,
posts: {},
postContainer: undefined,
};
function createImageLabel(img, postImages) {
var imageLabel = $('<div class="label"><span>'+ img.name +'</span></div>');
var closeButton = $('<button class="close">&times;</button>');
var uploadsInProgress = [];
closeButton.on('click', function(e) {
function createImagePlaceholder(img) {
var text = $('.post-window textarea').val(),
textarea = $('.post-window textarea'),
imgText = "!["+img.name+"](uploading...)";
imageLabel.remove();
var index = postImages.indexOf(img);
if(index !== -1) {
postImages.splice(index, 1);
}
text += imgText;
textarea.val(text + " ");
uploadsInProgress.push(1);
socket.emit("api:posts.uploadImage", img, function(err, data) {
var currentText = textarea.val();
imgText = "!["+data.name+"](uploading...)";
if(!err)
textarea.val(currentText.replace(imgText, "!["+data.name+"]("+data.url+")"));
else
textarea.val(currentText.replace(imgText, "!["+data.name+"](upload error)"));
uploadsInProgress.pop();
});
imageLabel.append(closeButton);
return imageLabel;
}
function loadFile(file) {
var reader = new FileReader(),
dropDiv = $('.post-window .imagedrop'),
imagelist = $('.post-window .imagelist'),
uuid = dropDiv.parents('[data-uuid]').attr('data-uuid'),
posts = composer.posts[uuid];
uuid = dropDiv.parents('[data-uuid]').attr('data-uuid');
$(reader).on('loadend', function(e) {
var bin = this.result;
var bin = this.result;
bin = bin.split(',')[1];
var img = {
@@ -40,20 +44,17 @@ define(['taskbar'], function(taskbar) {
data: bin
};
posts.images.push(img);
createImagePlaceholder(img);
var imageLabel = createImageLabel(img, posts.images);
imagelist.append(imageLabel);
dropDiv.hide();
});
reader.readAsDataURL(file);
}
function initializeFileReader() {
jQuery.event.props.push( "dataTransfer" );
var draggingDocument = false;
if(window.FileReader) {
@@ -77,26 +78,25 @@ define(['taskbar'], function(taskbar) {
drop.off('dragleave');
});
});
function cancel(e) {
e.preventDefault();
return false;
}
drop.on('dragover', cancel);
drop.on('dragenter', cancel);
drop.on('drop', function(e) {
e.preventDefault();
var uuid = drop.parents('[data-uuid]').attr('data-uuid'),
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;
@@ -109,8 +109,8 @@ define(['taskbar'], function(taskbar) {
var taskbar = document.getElementById('taskbar');
composer.postContainer = document.createElement('div');
composer.postContainer.className = 'post-window row-fluid';
composer.postContainer.innerHTML = '<div class="span5">' +
composer.postContainer.className = 'post-window row';
composer.postContainer.innerHTML = '<div class="col-md-5">' +
'<input type="text" tabIndex="1" placeholder="Enter your topic title here..." />' +
'<div class="btn-toolbar formatting-bar">' +
'<div class="btn-group">' +
@@ -121,11 +121,10 @@ define(['taskbar'], function(taskbar) {
'</div>' +
'</div>' +
'<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 data-action="minimize" class="btn hidden-xs" tabIndex="4"><i class="icon-download-alt"></i> Minimize</button>' +
'<button class="btn" data-action="discard" tabIndex="5"><i class="icon-remove"></i> Discard</button>' +
'<button data-action="post" class="btn" tabIndex="3"><i class="icon-ok"></i> Submit</button>' +
'</div>' +
@@ -151,13 +150,12 @@ define(['taskbar'], function(taskbar) {
cid: threadData.cid,
pid: threadData.pid,
title: threadData.title || '',
body: threadData.body || '',
images: []
body: threadData.body || ''
};
composer.load(uuid);
} else {
app.alert({
type: 'error',
type: 'danger',
timeout: 5000,
alert_id: 'post_error',
title: 'Please Log In to Post',
@@ -189,7 +187,15 @@ define(['taskbar'], function(taskbar) {
switch(action) {
case 'post': composer.post(uuid); break;
case 'minimize': composer.minimize(uuid); break;
case 'discard': composer.discard(uuid); break;
case 'discard':
if (postContentEl.value.length > 0) {
bootbox.confirm('Are you sure you wish to discard this post?', function(discard) {
if (discard) composer.discard(uuid);
});
} else {
composer.discard(uuid);
}
break;
}
});
@@ -265,18 +271,6 @@ 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'),
@@ -284,7 +278,7 @@ define(['taskbar'], function(taskbar) {
composer.reposition(post_uuid);
composer.active = post_uuid;
composer.postContainer.setAttribute('data-uuid', post_uuid);
if (parseInt(post_data.tid) > 0) {
titleEl.value = 'Replying to: ' + post_data.title;
@@ -299,7 +293,6 @@ define(['taskbar'], function(taskbar) {
}
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) {
@@ -312,7 +305,7 @@ define(['taskbar'], function(taskbar) {
}
composer.reposition = function(post_uuid) {
var postWindowEl = composer.postContainer.querySelector('.span5'),
var postWindowEl = composer.postContainer.querySelector('.col-md-5'),
taskbarBtn = document.querySelector('#taskbar [data-uuid="' + post_uuid + '"]'),
btnRect = taskbarBtn.getBoundingClientRect(),
taskbarRect = document.getElementById('taskbar').getBoundingClientRect(),
@@ -330,13 +323,23 @@ define(['taskbar'], function(taskbar) {
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(uploadsInProgress.length) {
return app.alert({
type: 'warning',
timeout: 2000,
title: 'Still uploading',
message: "Please wait for uploads to complete.",
alert_id: 'post_error'
});
}
if (titleEl.value.length < config.minimumTitleLength) {
return app.alert({
type: 'error',
type: 'danger',
timeout: 2000,
title: 'Title too short',
message: "Please enter a longer title. At least " + config.minimumTitleLength+ " characters.",
@@ -346,7 +349,7 @@ define(['taskbar'], function(taskbar) {
if (bodyEl.value.length < config.minimumPostLength) {
return app.alert({
type: 'error',
type: 'danger',
timeout: 2000,
title: 'Content too short',
message: "Please enter a longer post. At least " + config.minimumPostLength + " characters.",
@@ -359,21 +362,18 @@ define(['taskbar'], function(taskbar) {
socket.emit('api:topics.post', {
'title' : titleEl.value,
'content' : bodyEl.value,
'category_id' : postData.cid,
images: composer.posts[post_uuid].images
'category_id' : postData.cid
});
} else if (parseInt(postData.tid) > 0) {
socket.emit('api:posts.reply', {
'topic_id' : postData.tid,
'content' : bodyEl.value,
images: composer.posts[post_uuid].images
'content' : bodyEl.value
});
} else if (parseInt(postData.pid) > 0) {
socket.emit('api:posts.edit', {
pid: postData.pid,
content: bodyEl.value,
title: titleEl.value,
images: composer.posts[post_uuid].images
title: titleEl.value
});
}
@@ -383,7 +383,6 @@ define(['taskbar'], function(taskbar) {
composer.discard = function(post_uuid) {
if (composer.posts[post_uuid]) {
$(composer.postContainer).find('.imagedrop').hide();
$(composer.postContainer).find('.imagelist').empty();
delete composer.posts[post_uuid];
composer.minimize();
taskbar.discard('composer', post_uuid);

View File

@@ -99,10 +99,11 @@ define(function() {
mobileMenu.init = function() {
return; // disabling until this can be pluginified.
overlay = overlay || document.getElementById('mobile-menu-overlay');
menuBtn = menuBtn || document.getElementById('mobile-menu-btn');
postBtn = postBtn || document.getElementById('mobile-post-btn');
menuBtn.onclick = function() {
animateIcons();
}
@@ -111,7 +112,7 @@ define(function() {
displayCategories();
mobileMenu.onNavigate();
});
}
return {

View File

@@ -5,15 +5,15 @@ define(function() {
tasklist: undefined,
init: function() {
var footerEl = document.getElementById('footer');
taskbar.taskbar = document.createElement('div');
var jTaskbar = $(taskbar.taskbar);
taskbar.taskbar.innerHTML = '<div class="navbar-inner"><ul class="nav pull-right"></ul></div>';
taskbar.taskbar.className = 'taskbar navbar navbar-fixed-bottom';
taskbar.taskbar.innerHTML = '<div class="navbar-inner"><ul class="nav navbar-nav pull-right"></ul></div>';
taskbar.taskbar.className = 'taskbar navbar navbar-default navbar-fixed-bottom';
taskbar.taskbar.id = 'taskbar';
taskbar.tasklist = taskbar.taskbar.querySelector('ul');
document.body.insertBefore(taskbar.taskbar, footerEl);
document.body.insertBefore(taskbar.taskbar, footerEl.nextSibling);
// Posts bar events
jTaskbar.on('click', 'li', function() {
@@ -60,7 +60,7 @@ define(function() {
var element = $(taskbar.tasklist).find('li[data-uuid="'+uuid+'"]');
if(element.length)
return;
var btnEl = document.createElement('li');
btnEl.innerHTML = '<a href="#">' +

View File

@@ -1,7 +1,5 @@
(function (module) {
var config = {},
templates,
fs = null,
@@ -14,11 +12,11 @@
fs = require('fs');
} catch (e) {}
templates.force_refresh = function(tpl) {
templates.force_refresh = function (tpl) {
return !!config.force_refresh[tpl];
}
templates.get_custom_map = function(tpl) {
templates.get_custom_map = function (tpl) {
if (config['custom_mapping'] && tpl) {
for (var pattern in config['custom_mapping']) {
if (tpl.match(pattern)) {
@@ -30,11 +28,11 @@
return false;
}
templates.is_available = function(tpl) {
templates.is_available = function (tpl) {
return jQuery.inArray(tpl, available_templates) !== -1;
};
templates.ready = function(callback) {
templates.ready = function (callback) {
if (callback == null) {
if (this.ready_callback) {
this.ready_callback();
@@ -50,13 +48,13 @@
}
};
templates.prepare = function(raw_tpl, data) {
templates.prepare = function (raw_tpl, data) {
var template = {};
template.html = raw_tpl;
template.parse = parse;
template.blocks = {};
return template;
return template;
};
function loadTemplates(templatesToLoad) {
@@ -64,10 +62,10 @@
var loaded = templatesToLoad.length;
for (var t in templatesToLoad) {
(function(file) {
fs.readFile(global.configuration.ROOT_DIRECTORY + '/public/templates/' + file + '.tpl', function(err, html) {
var template = function() {
this.toString = function() {
(function (file) {
fs.readFile(__dirname + '/../templates/' + file + '.tpl', function (err, html) {
var template = function () {
this.toString = function () {
return this.html;
};
}
@@ -75,7 +73,7 @@
template.prototype.file = file;
template.prototype.parse = parse;
template.prototype.html = String(html);
global.templates[file] = new template;
loaded--;
@@ -86,147 +84,133 @@
}
function loadClient() {
jQuery.when(jQuery.getJSON(RELATIVE_PATH + '/templates/config.json'), jQuery.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function(config_data, templates_data) {
jQuery.when(jQuery.getJSON(RELATIVE_PATH + '/templates/config.json'), jQuery.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function (config_data, templates_data) {
config = config_data[0];
available_templates = templates_data[0];
templates.ready();
});
}
if (fs === null) loadClient();
else loadServer();
}
templates.init = function(templates_to_load) {
templates.init = function (templates_to_load) {
loadTemplates(templates_to_load || []);
}
templates.getTemplateNameFromUrl = function(url) {
templates.getTemplateNameFromUrl = function (url) {
var parts = url.split('?')[0].split('/');
for(var i=0; i<parts.length; ++i) {
for (var i = 0; i < parts.length; ++i) {
if (templates.is_available(parts[i])) {
return parts[i];
}
}
return '';
}
templates.load_template = function(callback, url, template) {
templates.load_template = function (callback, url, template) {
var location = document.location || window.location,
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : '');
api_url = (url === '' || url === '/') ? 'home' : url,
tpl_url = templates.get_custom_map(api_url.split('?')[0]),
trimmed = api_url;
var api_url = (url === '' || url === '/') ? 'home' : url;
var tpl_url = templates.get_custom_map(api_url.split('?')[0]);
var trimmed = api_url;
if(!tpl_url) {
if (!tpl_url) {
tpl_url = templates.getTemplateNameFromUrl(api_url);
}
var template_data = null;
var timestamp = new Date().getTime(); //debug
(function() {
var timestamp = new Date().getTime(); //debug
if (!templates[tpl_url]) {
jQuery.get(RELATIVE_PATH + '/templates/' + tpl_url + '.tpl?v=' + timestamp, function(html) {
var template = function() {
this.toString = function() {
return this.html;
};
}
template.prototype.parse = parse;
template.prototype.html = String(html);
template.prototype.blocks = {};
templates[tpl_url] = new template;
parse_template();
});
} else {
parse_template();
}
}());
(function() {
jQuery.get(API_URL + api_url, function(data) {
if(!data) {
ajaxify.go('404');
return;
if (!templates[tpl_url]) {
jQuery.get(RELATIVE_PATH + '/templates/' + tpl_url + '.tpl?v=' + timestamp, function (html) {
var template = function () {
this.toString = function () {
return this.html;
};
}
template_data = data;
parse_template();
}).fail(function(data) {
template_data = {};
template.prototype.parse = parse;
template.prototype.html = String(html);
template.prototype.blocks = {};
templates[tpl_url] = new template;
parse_template();
});
}());
} else {
parse_template();
}
jQuery.get(RELATIVE_PATH + '/api/' + api_url, function (data) {
if (!data) {
ajaxify.go('404');
return;
}
template_data = data;
parse_template();
}).fail(function (data) {
app.alertError("Can't load template data!");
});
function parse_template() {
if (!templates[tpl_url] || !template_data) return;
if(typeof global !== "undefined")
template_data['relative_path'] = nconf.get('relative_path');
else
template_data['relative_path'] = RELATIVE_PATH;
document.getElementById('content').innerHTML = templates[tpl_url].parse(template_data);
jQuery('#content [template-variable]').each(function(index, element) {
var value = null;
switch(element.getAttribute('template-type')) {
if (typeof global !== "undefined")
template_data['relative_path'] = nconf.get('relative_path');
else
template_data['relative_path'] = RELATIVE_PATH;
translator.translate(templates[tpl_url].parse(template_data), function (translatedTemplate) {
document.getElementById('content').innerHTML = translatedTemplate;
jQuery('#content [template-variable]').each(function (index, element) {
var value = null;
switch (element.getAttribute('template-type')) {
case 'boolean':
value = (element.value === 'true' || element.value === '1') ? true : false;
break;
case 'int': // Intentional fall-through
break;
case 'int': // Intentional fall-through
case 'integer':
value = parseInt(element.value);
break;
break;
default:
value = element.value;
break;
break;
}
templates.set(element.getAttribute('template-variable'), value);
});
if (callback) {
callback(true);
}
templates.set(element.getAttribute('template-variable'), value);
});
if (callback) {
callback(true);
}
}
}
templates.flush = function() {
templates.flush = function () {
parsed_variables = {};
}
templates.get = function(key) {
templates.get = function (key) {
return parsed_variables[key];
}
templates.set = function(key, value) {
templates.set = function (key, value) {
parsed_variables[key] = value;
}
//modified from https://github.com/psychobunny/dcp.templates
var parse = function(data) {
var parse = function (data) {
var self = this;
function replace(key, value, template) {
@@ -235,11 +219,11 @@
}
function makeRegex(block) {
return new RegExp("<!-- BEGIN " + block + " -->[^]*<!-- END " + block + " -->", 'g');
return new RegExp("<!-- BEGIN " + block + " -->[\\s\\S]*<!-- END " + block + " -->", 'g');
}
function getBlock(regex, block, template) {
data = template.match(regex);
data = template.match(regex);
if (data == null) return;
if (self.blocks && block !== undefined) self.blocks[block] = data[0];
@@ -255,7 +239,8 @@
return template.replace(regex, block);
}
var template = this.html, regex, block;
var template = this.html,
regex, block;
return (function parse(data, namespace, template) {
if (!data || data.length == 0) {
@@ -268,16 +253,18 @@
template = replace(namespace + d, '', template);
} else if (data[d].constructor == Array) {
namespace += d + '.';
var regex = makeRegex(d),
block = getBlock(regex, namespace.substring(0, namespace.length-1), template);
block = getBlock(regex, namespace.substring(0, namespace.length - 1), template);
if (block == null) {
namespace = namespace.replace(d + '.', '');
continue;
}
var numblocks = data[d].length - 1, i = 0, result = "";
var numblocks = data[d].length - 1,
i = 0,
result = "";
do {
result += parse(data[d][i], namespace, block);
@@ -287,7 +274,7 @@
template = setBlock(regex, result, template);
} else if (data[d] instanceof Object) {
namespace += d + '.';
regex = makeRegex(d),
block = getBlock(regex, namespace, template)
if (block == null) continue;
@@ -301,12 +288,12 @@
}
if (namespace) {
var regex = new RegExp("{" + namespace + "[^]*?}", 'g');
var regex = new RegExp("{" + namespace + "[\\s\\S]*?}", 'g');
template = template.replace(regex, '');
}
return template;
})(data, "", template);
}
@@ -315,7 +302,8 @@
templates.init();
}
})('undefined' === typeof module ? {module:{exports:{}}} : module)
})('undefined' === typeof module ? {
module: {
exports: {}
}
} : module)

165
public/src/translator.js Normal file
View File

@@ -0,0 +1,165 @@
(function (module) {
"use strict";
/*global RELATIVE_PATH*/
/*
* TODO:
*
* 1. language en is hardcoded while system is developed. to switch language packs for now please edit DEFAULT_LANGUAGE
* b. need to write fallback system to default language if keys are missing (is this even necessary?)
* 2. recursion needed when parsing language keys (ex. topics:modal.delete.title), right now json is all one level deep
* 3. server side settings for default language
* 4. user side settings for preferred language
*
*/
var DEFAULT_LANGUAGE = 'en';
var translator = {},
files = {
loaded: {},
loading: {},
callbacks: {} // could be combined with "loading" in future.
},
isServer = false;
module.exports = translator;
translator.get = function (key, callback) {
var parsedKey = key.split(':'),
languageFile = parsedKey[0];
parsedKey = parsedKey[1];
translator.load(languageFile, function (languageData) {
if (callback) {
callback(languageData[parsedKey]);
}
return languageData[parsedKey];
});
};
/*
* TODO: Not fully converted to server side yet, ideally server should be able to parse whole templates on demand if necessary
* fix: translator.load should determine if server side and immediately return appropriate language file.
*/
translator.translate = function (data, callback) {
var keys = data.match(/\[\[.*?\]\]/g),
loading = 0;
function insertLanguage(text, key, value, variables) {
if (value) {
for (var i = 1, ii = variables.length; i < ii; i++) {
var variable = variables[i].replace(']]', '');
value = value.replace('%' + i, variable);
}
text = text.replace(key, value);
}
return text;
}
for (var key in keys) {
if (keys.hasOwnProperty(key)) {
var variables = keys[key].split(/[,][?\s+]/);
var parsedKey = keys[key].replace('[[', '').replace(']]', '').split(':'),
languageFile = parsedKey[0];
parsedKey = parsedKey[1].split(',')[0];
if (files.loaded[languageFile]) {
data = insertLanguage(data, keys[key], files.loaded[languageFile][parsedKey], variables);
} else {
loading++;
(function (languageKey, parsedKey) {
translator.load(languageFile, function (languageData) {
data = insertLanguage(data, languageKey, languageData[parsedKey], variables);
loading--;
checkComplete();
});
}(keys[key], parsedKey));
}
}
}
checkComplete();
function checkComplete() {
if (loading === 0) {
callback(data);
}
}
};
translator.load = function (filename, callback) {
if (isServer === true) {
if (callback) {
callback(files.loaded[filename]);
}
return files.loaded[filename];
}
if (files.loaded[filename] && !files.loading[filename]) {
if (callback) {
callback(files.loaded[filename]);
}
} else if (files.loading[filename]) {
if (callback) {
files.callbacks[filename] = files.callbacks[filename] || [];
files.callbacks[filename].push(callback);
}
} else {
var timestamp = new Date().getTime(); //debug
files.loading[filename] = true;
jQuery.getJSON(RELATIVE_PATH + '/language/' + DEFAULT_LANGUAGE + '/' + filename + '.json?v=' + timestamp, function (language) {
files.loaded[filename] = language;
if (callback) {
callback(language);
}
while (files.callbacks[filename] && files.callbacks[filename].length) {
files.callbacks[filename].pop()(language);
}
files.loading[filename] = false;
});
}
};
translator.loadServer = function () {
isServer = true;
var utils = require('./utils.js'),
path = require('path'),
fs = require('fs');
utils.walk(path.join(__dirname, '../../', 'public/language/' + DEFAULT_LANGUAGE), function (err, data) {
var loaded = data.length;
for (var d in data) {
if (data.hasOwnProperty(d)) {
files.loaded[path.basename(data[d]).replace('.json', '')] = require(data[d]);
}
}
});
};
if ('undefined' !== typeof window) {
window.translator = module.exports;
}
})('undefined' === typeof module ? {
module: {
exports: {}
}
} : module);

View File

@@ -1,5 +1,5 @@
(function (module) {
(function(module) {
var utils, fs;
try {
@@ -10,15 +10,18 @@
module.exports = utils = {
generateUUID: function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
},
//Adapted from http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
walk: function(dir, done) {
var main_dir = global.configuration.ROOT_DIRECTORY + '/public/templates/';
var results = [];
var results = [],
path = require('path'),
main_dir = path.join(__dirname, '..', 'templates');
fs.readdir(dir, function(err, list) {
if (err) return done(err);
var pending = list.length;
@@ -32,22 +35,22 @@
if (!--pending) done(null, results);
});
} else {
results.push(file.replace(main_dir, '').replace('.tpl', ''));
results.push(file.replace(main_dir + '/', '').replace('.tpl', ''));
if (!--pending) done(null, results);
}
});
});
});
},
relativeTime: function(timestamp, min) {
var now = +new Date(),
var now = +new Date(),
difference = now - Math.floor(parseFloat(timestamp));
difference = Math.floor(difference / 1000);
if (difference < 60) return difference + (min ? 's' : ' second') + (difference !== 1 && !min ? 's' : '');
difference = Math.floor(difference / 60);
if (difference < 60) return difference + (min ? 'm' : ' minute') + (difference !== 1 && !min ? 's' : '');
@@ -63,7 +66,7 @@
difference = Math.floor(difference / 12);
return difference + (min ? 'y' : ' year') + (difference !== 1 && !min ? 's' : '');
},
//http://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/
slugify: function(str) {
str = str.replace(/^\s+|\s+$/g, ''); // trim
@@ -71,14 +74,14 @@
// remove accents, swap ñ for n, etc
var from = "àáäâèéëêìíïîıòóöôùúüûñçşğ·/_,:;";
var to = "aaaaeeeeiiiiioooouuuuncsg------";
for (var i=0, l=from.length ; i<l ; i++) {
var to = "aaaaeeeeiiiiioooouuuuncsg------";
for (var i = 0, l = from.length; i < l; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-'); // collapse dashes
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-'); // collapse dashes
return str;
},
@@ -91,31 +94,31 @@
},
isUserNameValid: function(name) {
return (name && name !== "" && (/^[a-zA-Z0-9 _-]{3,14}$/.test(name)));
return (name && name !== "" && (/^[a-zA-Z0-9 _-]+$/.test(name)));
},
isPasswordValid: function(password) {
return password && password.indexOf(' ') === -1 && password.length > 5;
return password && password.indexOf(' ') === -1;
},
// Blatently stolen from: http://phpjs.org/functions/strip_tags/
'strip_tags': function(input, allowed) {
allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
return input.replace(commentsAndPhpTags, '').replace(tags, function($0, $1) {
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
});
},
buildMetaTags: function(tagsArr) {
var tags = '',
var tags = '',
tag;
for(var x=0,numTags=tagsArr.length;x<numTags;x++) {
for (var x = 0, numTags = tagsArr.length; x < numTags; x++) {
if (tags.length > 0) tags += "\n\t";
tag = '<meta';
for(y in tagsArr[x]) {
for (y in tagsArr[x]) {
tag += ' ' + y + '="' + tagsArr[x][y] + '"';
}
tag += ' />';
@@ -128,14 +131,16 @@
refreshTitle: function(url) {
if (!url) {
var a = document.createElement('a');
var a = document.createElement('a');
a.href = document.location;
url = a.pathname.slice(1);
}
var notificationIcon;
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';
document.title = (numNotifications > 0 ? '(' + numNotifications + ') ' : '') + title;
notificationIcon = notificationIcon || document.querySelector('.notifications a i');
if (numNotifications > 0 && notificationIcon) notificationIcon.className = 'icon-circle active';
});
jQuery.getJSON(RELATIVE_PATH + '/api/unread/total', function(data) {
@@ -146,8 +151,7 @@
badge
.removeClass('badge-inverse')
.addClass('badge-important')
}
else {
} else {
badge
.removeClass('badge-important')
.addClass('badge-inverse')
@@ -156,31 +160,43 @@
},
isRelativeUrl: function(url) {
var firstChar = url.slice(0, 1);
var firstChar = url.slice(0, 1);
return (firstChar === '.' || firstChar === '/');
}
}
if (!String.prototype.trim) {
String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');};
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
if (!String.prototype.ltrim) {
String.prototype.ltrim=function(){return this.replace(/^\s+/,'');};
}
if (!String.prototype.rtrim) {
String.prototype.rtrim=function(){return this.replace(/\s+$/,'');};
}
if (!String.prototype.fulltrim) {
String.prototype.fulltrim=function(){return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ');};
String.prototype.ltrim = function() {
return this.replace(/^\s+/, '');
};
}
if (!String.prototype.rtrim) {
String.prototype.rtrim = function() {
return this.replace(/\s+$/, '');
};
}
if (!String.prototype.fulltrim) {
String.prototype.fulltrim = function() {
return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '').replace(/\s+/g, ' ');
};
}
if ('undefined' !== typeof window) {
window.utils = module.exports;
}
})('undefined' === typeof module ? {module:{exports:{}}} : module)
})('undefined' === typeof module ? {
module: {
exports: {}
}
} : module)

View File

@@ -1,4 +1,4 @@
<div class="alert alert-error">
<strong>Access Denied</strong>
<p>You seem to have stumbled upon a page that you do not have access to. Perhaps you should <a href="/login">try logging in?</a></p>
<div class="alert alert-danger">
<strong>[[global:403.title]]</strong>
<p>[[global:403.message]]</p>
</div>

View File

@@ -1,4 +1,4 @@
<div class="alert alert-error">
<strong>Not found</strong>
<p>You seem to have stumbled upon a page that does not exist. Return to the <a href="/">home page</a></p>
<div class="alert alert-danger">
<strong>[[global:404.title]]</strong>
<p>[[global:404.message]]</p>
</div>

View File

@@ -1,30 +1,30 @@
<div class="well">
<div class="well account">
<div class="account-username-box" data-userslug="{userslug}">
<span class="account-username">
<a href="/users/{userslug}">{username}</a>
<a href="/user/{userslug}">{username}</a>
</span>
</div>
<div class="row-fluid">
<div class="span2 account-block" style="text-align: center; margin-bottom:20px;">
<div class="row">
<div class="col-md-2 account-block" style="text-align: center; margin-bottom:20px;">
<div class="account-picture-block">
<img src="{picture}" class="user-profile-picture img-polaroid"/>
<img src="{picture}" class="user-profile-picture img-thumbnail"/>
</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>
<span class="label label-danger">banned</span>
</div>
<div id="user-actions">
<a id="follow-btn" href="#" class="btn hide">Follow</a>
<a id="unfollow-btn" href="#" class="btn hide">Unfollow</a>
<a id="follow-btn" href="#" class="btn btn-default">Follow</a>
<a id="unfollow-btn" href="#" class="btn btn-default">Unfollow</a>
</div>
</div>
<div class="span4">
<div class="col-md-4">
<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>
@@ -47,8 +47,8 @@
<span>{age}</span>
<br/>
<hr/>
<span class="account-bio-label">member for</span>
<span>{joindate}</span>
<span class="account-bio-label">joined</span>
<span class="timeago" title="{joindate}"></span>
<br/>
<span class="account-bio-label">profile views</span>
@@ -80,11 +80,11 @@
</div>
</div>
<div class="span6 user-recent-posts">
<div class="col-md-6 user-recent-posts">
<!-- BEGIN posts -->
<div class="topic-row img-polaroid clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
<div class="topic-row img-thumbnail clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
<span>{posts.content}</span>
<span class="pull-right">{posts.relativeTime} ago</span>
<span class="pull-right timeago" title="{posts.relativeTime}"></span>
</div>
<!-- END posts -->
</div>

View File

@@ -1,165 +1,178 @@
<div class="well">
<div id="change-picture-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="Change Picture" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Change Picture</h3>
</div>
<div class="modal-body">
<div id="gravatar-box">
<img id="user-gravatar-picture" src="" class="img-thumbnail user-profile-picture">
<span class="user-picture-label">Gravatar</span>
<i class='icon-ok icon-2x'></i>
</div>
<br/>
<div id="uploaded-box">
<img id="user-uploaded-picture" src="" class="img-thumbnail user-profile-picture">
<span class="user-picture-label">Uploaded picture</span>
<i class='icon-ok icon-2x'></i>
</div>
<!-- Change Picture Modal -->
<div id="change-picture-modal" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Change Picture</h3>
</div>
<div class="modal-body">
<a id="uploadPictureBtn" href="#">Upload new picture</a>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
<button id="savePictureChangesBtn" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div id="gravatar-box">
<img id="user-gravatar-picture" src="" class="img-polaroid user-profile-picture">
<span class="user-picture-label">Gravatar</span>
<i class='icon-ok icon-2x'></i>
</div>
<br/>
<div id="uploaded-box">
<img id="user-uploaded-picture" src="" class="img-polaroid user-profile-picture">
<span class="user-picture-label">Uploaded picture</span>
<i class='icon-ok icon-2x'></i>
</div>
<a id="uploadPictureBtn" href="#">Upload new picture</a>
</div>
<div id="upload-picture-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="Upload Picture" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Upload Picture</h3>
</div>
<div class="modal-body">
<form id="uploadForm" action="{relative_path}/user/uploadpicture" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="userPhoto">Upload a picture</label>
<input type="file" id="userPhotoInput" name="userPhoto">
<p class="help-block">You may only upload PNG, JPG, or GIF files under 256kb.</p>
</div>
<input id="imageUploadCsrf" type="hidden" name="_csrf" value="" />
</form>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button id="savePictureChangesBtn" class="btn btn-primary">Save changes</button>
</div>
</div>
<div id="upload-progress-box" class="progress progress-striped">
<div id="upload-progress-bar" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0" aria-valuemin="0">
<span class="sr-only"> success</span>
</div>
</div>
<div id="alert-status" class="alert alert-info hide"></div>
<div id="alert-success" class="alert alert-success hide"></div>
<div id="alert-error" class="alert alert-danger hide"></div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
<button id="pictureUploadSubmitBtn" class="btn btn-primary">Upload Picture</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Upload picture modal-->
<div id="upload-picture-modal" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Upload Picture</h3>
</div>
<div class="modal-body">
<form id="uploadForm" action="{relative_path}/users/uploadpicture" method="post" enctype="multipart/form-data">
<input id="userPhotoInput" type="file" name="userPhoto" />
<input id="imageUploadCsrf" type="hidden" name="_csrf" value="" />
</form>
<div id="upload-progress-box" class="progress progress-striped active hide">
<div id="upload-progress-bar" class="bar" style="width: 0%;"></div>
</div>
<div id="alert-status" class="alert hide"></div>
<div id="alert-success" class="alert alert-success hide"></div>
<div id="alert-error" class="alert alert-error hide"></div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button id="pictureUploadSubmitBtn" class="btn btn-primary">Upload Picture</button>
</div>
</div>
<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>
<a href="/user/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/user/{userslug}/edit">edit</a>
</span>
</div>
<div class="row-fluid">
<div class="span2" style="text-align: center; margin-bottom:20px;">
<div class="row">
<div class="col-md-2" style="text-align: center; margin-bottom:20px;">
<div class="account-picture-block text-center">
<img id="user-current-picture" class="user-profile-picture img-polaroid" src="{picture}" /><br/>
<img id="user-current-picture" class="user-profile-picture img-thumbnail" src="{picture}" /><br /><br />
<a id="changePictureBtn" href="#" class="btn btn-primary">change picture</a>
</div>
</div>
<div class="span10">
<div class="inline-block">
<div class="col-md-5">
<div>
<form class='form-horizontal'>
<div class="control-group">
<label class="control-label" for="inputEmail">Email</label>
<div class="controls">
<input type="text" id="inputEmail" placeholder="Email" value="{email}">
<input class="form-control" type="text" id="inputEmail" placeholder="Email" value="{email}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputFullname">Full Name</label>
<div class="controls">
<input type="text" id="inputFullname" placeholder="Full Name" value="{fullname}">
<input class="form-control" type="text" id="inputFullname" placeholder="Full Name" value="{fullname}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputWebsite">Website</label>
<div class="controls">
<input type="text" id="inputWebsite" placeholder="http://website.com" value="{website}">
<input class="form-control" type="text" id="inputWebsite" placeholder="http://website.com" value="{website}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputLocation">Location</label>
<div class="controls">
<input type="text" id="inputLocation" placeholder="Location" value="{location}">
<input class="form-control" type="text" id="inputLocation" placeholder="Location" value="{location}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputBirthday">Birthday</label>
<div class="controls">
<input type="date" id="inputBirthday" placeholder="mm/dd/yyyy" value="{birthday}">
<input class="form-control" type="date" id="inputBirthday" placeholder="mm/dd/yyyy" value="{birthday}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputSignature">Signature</label>
<div class="controls">
<textarea id="inputSignature" placeholder="max 150 chars" rows="5">{signature}</textarea>
<textarea class="form-control" id="inputSignature" placeholder="max 150 chars" rows="5">{signature}</textarea>
</div>
</div>
<input type="hidden" id="inputUID" value="{uid}">
<input type="hidden" id="inputUID" value="{uid}"><br />
<div class="form-actions">
<a id="submitBtn" href="#" class="btn btn-primary">Save changes</a>
</div>
</form>
</div>
<div class="inline-block" style="vertical-align:top;">
<hr/>
</div>
<div class="col-md-5">
<div style="vertical-align:top;">
<form class='form-horizontal'>
<div class="control-group">
<label class="control-label" for="inputCurrentPassword">Current Password</label>
<div class="controls">
<input type="password" id="inputCurrentPassword" placeholder="Current Password" value="">
<input class="form-control" type="password" id="inputCurrentPassword" placeholder="Current Password" value="">
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputNewPassword">Password</label>
<div class="controls">
<input type="password" id="inputNewPassword" placeholder="New Password" value=""><br/><span id="password-notify" class="label label-important"></span>
<input class="form-control" type="password" id="inputNewPassword" placeholder="New Password" value="">
<div id="password-notify" class="alert alert-danger hide"></div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputNewPasswordAgain">Confirm Password</label>
<div class="controls">
<input type="password" id="inputNewPasswordAgain" placeholder="Confirm Password" value=""><br/><span id="password-confirm-notify" class="label label-important"></span>
<input class="form-control" type="password" id="inputNewPasswordAgain" placeholder="Confirm Password" value="">
<div id="password-confirm-notify" class="alert alert-danger hide"></div>
</div>
</div>
<br/>
<div class="form-actions">
<a id="changePasswordBtn" href="#" class="btn btn-primary">Change Password</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" />

View File

@@ -1,25 +1,27 @@
<div class="well">
<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>
<a href="/user/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/user/{userslug}/settings">settings</a>
</span>
</div>
<div class="row-fluid">
<div class="span6">
<div class="row">
<div class="col-md-6">
<h4>privacy</h4>
<label class="checkbox">
<input id="showemailCheckBox" type="checkbox" {showemail}> Show my email
</label>
<div class="checkbox">
<label>
<input id="showemailCheckBox" type="checkbox" {showemail}> Show my email
</label>
</div>
</div>
<div class="span6">
<div class="col-md-6">
</div>
</div>
</div>
<div class="form-actions">
<a id="submitBtn" href="#" class="btn btn-primary">Save changes</a>
</div>

File diff suppressed because one or more lines are too long

View File

@@ -2,20 +2,25 @@
<hr />
<form>
<div class="alert alert-notify">
<div class="alert alert-warning">
<p>
Create a <strong>Facebook Application</strong> via the
<a href="https://developers.facebook.com/">Facebook Developers Page</a> and
then paste your application details here.
</p>
<br />
<input type="text" data-field="social:facebook:app_id" title="Application ID" class="input-medium" placeholder="App ID"><br />
<input type="text" data-field="social:facebook:secret" title="Application Secret" class="input-large" placeholder="App Secret"><br />
<input type="text" data-field="social:facebook:app_id" title="Application ID" class="form-control input-lg" placeholder="App ID"><br />
<input type="text" data-field="social:facebook:secret" title="Application Secret" class="form-control input-md" placeholder="App Secret"><br />
</div>
</form>
<button class="btn btn-large btn-primary" id="save">Save</button>
<button class="btn btn-lg btn-primary" id="save">Save</button>
<script>
nodebb_admin.prepare();
var loadDelay = setInterval(function() {
if (nodebb_admin) {
nodebb_admin.prepare();
clearInterval(loadDelay);
}
}, 500);
</script>

View File

@@ -1,6 +1,9 @@
</div>
</div>
<div id="alert_window"></div>
<div id="footer" class="container" style="padding-top: 50px; display:none;">
<footer class="footer">Copyright &copy; 2013 <a target="_blank" href="http://www.nodebb.com">NodeBB</a> by <a target="_blank" href="https://github.com/psychobunny">psychobunny</a>, <a href="https://github.com/julianlam" target="_blank">julianlam</a>, <a href="https://github.com/barisusakli" target="_blank">barisusakli</a> from <a target="_blank" href="http://www.designcreateplay.com">designcreateplay</a></footer>
</div>

View File

@@ -2,20 +2,25 @@
<hr />
<form>
<div class="alert alert-notify">
<div class="alert alert-warning">
<p>
Create a <strong>Google Application</strong> via the
<a href="https://code.google.com/apis/console/">API Console</a> and then paste
your application details here.
</p>
<br />
<input type="text" data-field="social:google:id" title="Client ID" class="input-xxlarge" placeholder="Client ID"><br />
<input type="text" data-field="social:google:secret" title="Client Secret" class="input-large" placeholder="Client Secret"><br />
<input type="text" data-field="social:google:id" title="Client ID" class="form-control input-lg" placeholder="Client ID"><br />
<input type="text" data-field="social:google:secret" title="Client Secret" class="form-control" placeholder="Client Secret"><br />
</div>
</form>
<button class="btn btn-large btn-primary" id="save">Save</button>
<button class="btn btn-lg btn-primary" id="save">Save</button>
<script>
nodebb_admin.prepare();
var loadDelay = setInterval(function() {
if (nodebb_admin) {
nodebb_admin.prepare();
clearInterval(loadDelay);
}
}, 500);
</script>

View File

@@ -0,0 +1,100 @@
<h1>Groups</h1>
<hr />
<div class="groups">
<ul id="groups-list">
<!-- BEGIN groups -->
<li data-gid="{groups.gid}">
<div class="row">
<div class="col-lg-8">
<h2>{groups.name}</h2>
<p>{groups.description}</p>
<div class="btn-group">
<button class="btn btn-default" data-action="members">Members</button>
<button class="btn btn-danger" data-action="delete">Delete Group</button>
</div>
</div>
<div class="col-lg-4">
<ul class="pull-right members">
<!-- BEGIN members -->
<li data-uid="{groups.members.uid}" title="{groups.members.username}"><img src="{groups.members.picture}" /></li>
<!-- END members -->
</ul>
</div>
</div>
</li>
<!-- END groups -->
</ul>
<div class="text-center">
<button class="btn btn-primary" id="create">New Group</button>
</div>
<div class="modal fade" id="create-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Create Group</h4>
</div>
<div class="modal-body">
<div class="alert alert-danger hide" id="create-modal-error"></div>
<form>
<div class="form-group">
<label for="group-name">Group Name</label>
<input type="text" class="form-control" id="create-group-name" placeholder="Group Name" />
</div>
<div class="form-group">
<label for="group-name">Description</label>
<input type="text" class="form-control" id="create-group-desc" placeholder="A short description about your group" />
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="create-modal-go">Create</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="group-details-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Manage Group</h4>
</div>
<div class="modal-body">
<div class="alert alert-danger hide" id="create-modal-error"></div>
<form>
<div class="form-group">
<label for="group-name">Group Name</label>
<input type="text" class="form-control" id="change-group-name" placeholder="Group Name" />
</div>
<div class="form-group">
<label for="group-name">Description</label>
<input type="text" class="form-control" id="change-group-desc" placeholder="A short description about your group" />
</div>
<div class="form-group">
<label>Members</label>
<p>Click on a user to remove them from the group</p>
<ul class="members current_members" id="group-details-members"></ul>
</div>
<div class="form-group">
<label for="add-member">Add User to Group</label>
<input type="text" class="form-control" id="group-details-search" placeholder="Search Users" />
<ul class="members" id="group-details-search-results"></ul>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/groups.js"></script>

View File

@@ -16,9 +16,12 @@
<script type="text/javascript" src="{relative_path}/socket.io/socket.io.js"></script>
<script type="text/javascript" src="{relative_path}/src/app.js"></script>
<script type="text/javascript" src="{relative_path}/src/templates.js"></script>
<script type="text/javascript" src="{relative_path}/src/translator.js"></script>
<script type="text/javascript" src="{relative_path}/src/ajaxify.js"></script>
<script src="{relative_path}/vendor/jquery/js/jquery.timeago.js"></script>
<script src="{relative_path}/vendor/requirejs/require.js"></script>
<script src="{relative_path}/vendor/bootbox/bootbox.min.js"></script>
<script>
require.config({
baseUrl: "{relative_path}/src/modules",
@@ -28,52 +31,54 @@
<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/nodebb.css" />
<link rel="stylesheet" type="text/css" href="{relative_path}/css/admin.css" />
</head>
<body class="admin">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="brand" href="/admin/index">NodeBB ACP</a>
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="nav-collapse collapse">
<ul class="nav">
<li>
<a href="/" target="_blank"><i class="icon-book"></i> Forum</a>
</li>
<li>
<a href="/admin/index"><i class="icon-home"></i> Home</a>
</li>
<li>
<a href="/admin/settings"><i class="icon-cogs"></i> Settings</a>
</li>
</ul>
<ul class="nav pull-right" id="right-menu">
<li><a href="/users" id="user_label"></a></li>
</ul>
</div>
<a class="navbar-brand" href="/admin/index">NodeBB ACP</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li>
<a href="/" target="_blank"><i class="icon-book"></i> Forum</a>
</li>
<li>
<a href="/admin/index"><i class="icon-home"></i> Home</a>
</li>
<li>
<a href="/admin/settings"><i class="icon-cogs"></i> Settings</a>
</li>
<li>
<a href="#" id="reconnect"></a>
</li>
</ul>
<ul class="nav pull-right" id="right-menu">
<li><a href="/users" id="user_label"></a></li>
</ul>
</div>
</div>
</div>
<div id="alert_window"></div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span3">
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="well sidebar-nav">
<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/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/groups"><i class="icon-group"></i> Groups</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>
@@ -102,4 +107,4 @@
</ul>
</div><!--/.well -->
</div><!--/span-->
<div class="span9" id="content">
<div class="col-md-9" id="content">

View File

@@ -1,14 +1,14 @@
<div class="hero-unit">
<div class="jumbotron">
<h1>Welcome to NodeBB</h1>
<br />
<p>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-large"><i class="icon-comment"></i> NodeBB Forum</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-large"><i class="icon-github-alt"></i> Get Plugins</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-large"><i class="icon-github-alt"></i> Get Themes</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-large"><i class="icon-twitter"></i> dcplabs</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-default btn-lg"><i class="icon-comment"></i> NodeBB Forum</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-default btn-lg"><i class="icon-github-alt"></i> Get Plugins</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-default btn-lg"><i class="icon-github-alt"></i> Get Themes</a>
<a target="_blank" href="http://www.nodebb.org" class="btn btn-default btn-lg"><i class="icon-twitter"></i> dcplabs</a>
</p>
<p><small>You are running <strong>NodeBB v{version}</strong>. This is where we will check to make sure your <strong>NodeBB</strong> is latest, etc.</small></p>
<p><small>You are running <strong>NodeBB v{version}</strong>. Always make sure that your <strong>NodeBB</strong> is up to date for the latest security patches and bug fixes.</small></p>
</div>

View File

@@ -1,7 +1,7 @@
<h1>MOTD</h1>
<div class="alert motd">
<hr />
<div class="alert alert-warning motd">
<p>
The <strong>Message of the Day</strong> (MOTD) is typically a message shown to users when they first log into a forum or chat room.
In NodeBB, the MOTD is present at the top of the forum homepage, and can be customized much like a header.
@@ -9,14 +9,25 @@
<p>
You can enter either full HTML or Markdown text.
</p>
<textarea placeholder="Welcome to NodeBB!" data-field="motd" rows="10"></textarea>
<br />
<textarea class="form-control" placeholder="Welcome to NodeBB!" data-field="motd" rows="10"></textarea>
<br />
<form class="form-inline">
<label class="checkbox" for="show_motd"><input type="checkbox" id="show_motd" data-field="show_motd" /> Show the Message of the Day</label>
<div class="checkbox">
<label for="show_motd">
<input type="checkbox" id="show_motd" data-field="show_motd" /> Show the Message of the Day
</label>
</div>
</form>
</div>
<button class="btn btn-large btn-primary" id="save" checked>Save</button>
<button class="btn btn-lg btn-primary" id="save" checked>Save</button>
<script>
nodebb_admin.prepare();
var loadDelay = setInterval(function() {
if (nodebb_admin) {
nodebb_admin.prepare();
clearInterval(loadDelay);
}
}, 500);
</script>

View File

@@ -13,9 +13,9 @@
<!-- END plugins -->
</ul>
<div class="alert">
<div class="alert alert-warning">
<p>
<strong>Interesed in writing plugins for NodeBB?</strong>
<strong>Interested 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>.

View File

@@ -2,66 +2,85 @@
<hr />
<h3>General Settings</h3>
<div class="alert">
<div class="alert alert-warning">
<form>
<label>Site Title</label>
<input type="text" placeholder="Your Community Name" data-field="title" />
<input class="form-control" type="text" placeholder="Your Community Name" data-field="title" /><br />
<label>Site Description</label>
<input type="text" class="input-xxlarge" placeholder="A short description about your community" data-field="description" />
<input type="text" class="form-control" placeholder="A short description about your community" data-field="description" /><br />
<label>Imgur Client ID</label>
<input type="text" class="input-xxlarge" placeholder="Imgur ClientID for image uploads" data-field="imgurClientID" />
<input type="text" class="form-control" placeholder="Imgur ClientID for image uploads" data-field="imgurClientID" /><br />
<label>Maximum User Image Size</label>
<input type="text" class="form-control" placeholder="Maximum size of uploaded user images in kilobytes" data-field="maximumProfileImageSize" />
</form>
</div>
<form>
<h3>Privilege Thresholds</h3>
<div class="alert alert-notify">
<div class="alert alert-warning">
<p>Use <strong>privilege thresholds</strong> to manage how much reputation a user must gain to receive moderator access.</p><br />
<strong>Manage Thread</strong><br /> <input type="text" class="" value="1000"><br />
<strong>Moderate Users</strong><br /> <input type="text" class="" value="10000"><br />
<strong>Create Pinned Topics</strong><br /> <input type="text" class="" value="100000"><br />
<strong>Manage Thread</strong><br /> <input type="text" class="form-control" value="1000"><br />
<strong>Moderate Users</strong><br /> <input type="text" class="form-control" value="10000"><br />
<strong>Create Pinned Topics</strong><br /> <input type="text" class="form-control" value="100000"><br />
</div>
</form>
<form>
<h3>Email Settings</h3>
<div class="alert alert-notify">
<div class="alert alert-warning">
<div>
<p>
<strong>Email Address</strong><br />
The following email address refers to the email that the recipient will see in the "From" and "Reply To" fields.
</p>
<input type="text" class="input-large" data-field="email:from" placeholder="info@example.org" />
<input type="text" class="form-control input-lg" data-field="email:from" placeholder="info@example.org" /><br />
</div>
<div>
<p>
<strong>SMTP Server Host</strong><br />
(Default: <em>127.0.0.1</em>)
</p>
<input type="text" class="input-medium" data-field="email:smtp:host" placeholder="127.0.0.1" />
<input type="text" class="form-control input-md" data-field="email:smtp:host" placeholder="127.0.0.1" /><br />
</div>
<div>
<p>
<strong>SMTP Server Port</strong>
</p>
<input type="text" class="input-mini" data-field="email:smtp:port" placeholder="25" />
<input type="text" class="form-control input-md" data-field="email:smtp:port" placeholder="25" /><br />
</div>
</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 />
<h3>User Settings</h3>
<div class="alert alert-warning">
<strong>Minimum Username Length</strong><br />
<input type="text" class="form-control" value="2" data-field="minimumUsernameLength"><br />
<strong>Maximum Username Length</strong><br />
<input type="text" class="form-control" value="16" data-field="maximumUsernameLength"><br />
<strong>Minimum Password Length</strong><br />
<input type="text" class="form-control" value="6" data-field="minimumPasswordLength"><br />
</div>
</form>
<button class="btn btn-large btn-primary" id="save">Save</button>
<form>
<h3>Post Settings</h3>
<div class="alert alert-warning">
<strong>Post Delay</strong><br /> <input type="text" class="form-control" value="10000" data-field="postDelay"><br />
<strong>Minimum Title Length</strong><br /> <input type="text" class="form-control" value="3" data-field="minimumTitleLength"><br />
<strong>Minimum Post Length</strong><br /> <input type="text" class="form-control" value="8" data-field="minimumPostLength"><br />
</div>
</form>
<button class="btn btn-lg btn-primary" id="save">Save</button>
<script>
nodebb_admin.prepare();
var loadDelay = setInterval(function() {
if (nodebb_admin) {
nodebb_admin.prepare();
clearInterval(loadDelay);
}
}, 500);
</script>

View File

@@ -10,7 +10,7 @@ jQuery(document).ready(function () {
QUnit.init();
asyncTest( "Loading Categories", function() {
jQuery.get(config.api_url + 'home', function(data) {
jQuery.get(RELATIVE_PATH + '/api/home', function(data) {
ok( data.categories.length > 0, JSON.stringify(data.categories) );
start();

View File

@@ -20,7 +20,7 @@
</ul>
<div class="text-center">
<button id="topics_loadmore" class="btn btn-large">Load More Topics</button>
<button id="topics_loadmore" class="btn btn-primary btn-lg">Load More Topics</button>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/admin/topics.js"></script>

View File

@@ -2,20 +2,25 @@
<hr />
<form>
<div class="alert alert-notify">
<div class="alert alert-warning">
<p>
Create a <strong>Twitter Application</strong> via the
<a href="https://dev.twitter.com/">Twitter Developers Page</a> and then
paste your application details here.
</p>
<br />
<input type="text" data-field="social:twitter:key" title="Consumer Key" class="input-large" placeholder="Consumer Key"><br />
<input type="text" data-field="social:twitter:secret" title="Consumer Secret" class="input-xlarge" placeholder="Consumer Secret">
<input type="text" data-field="social:twitter:key" title="Consumer Key" class="form-control input-lg" placeholder="Consumer Key"><br />
<input type="text" data-field="social:twitter:secret" title="Consumer Secret" class="form-control input-md" placeholder="Consumer Secret">
</div>
</form>
<button class="btn btn-large btn-primary" id="save">Save</button>
<button class="btn btn-lg btn-primary" id="save">Save</button>
<script>
nodebb_admin.prepare();
var loadDelay = setInterval(function() {
if (nodebb_admin) {
nodebb_admin.prepare();
clearInterval(loadDelay);
}
}, 500);
</script>

View File

@@ -7,21 +7,21 @@
<li class=''><a href='/admin/users/search'>Search</a></li>
</ul>
<br />
<div class="search {search_display} well">
<input id="search-user" type="text" placeholder="Enter a username to search"/><br />
<input class="form-control" id="search-user" type="text" placeholder="Enter a username to search"/><br />
<i class="icon-spinner icon-spin none"></i>
<span id="user-notfound-notify" class="label label-important hide">User not found!</span><br/>
<span id="user-notfound-notify" class="label label-danger hide">User not found!</span><br/>
</div>
<ul id="users-container" class="users">
<ul id="users-container" class="users admin">
<!-- BEGIN users -->
<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 href="/user/{users.userslug}">
<img src="{users.picture}" class="img-thumbnail"/>
</a>
<br/>
<a href="/users/{users.userslug}">{users.username}</a>
<a href="/user/{users.userslug}">{users.username}</a>
<br/>
<div title="reputation">
<span id='reputation'>{users.reputation}</span>
@@ -32,20 +32,14 @@
<i class='icon-pencil'></i>
</div>
<div>
<a href="#" class="btn admin-btn">Admin</a>
</div>
<div>
<a href="#" class="btn delete-btn btn-danger">Delete</a>
</div>
<div>
<a href="#" class="btn ban-btn">Ban</a>
<a href="#" class="btn btn-default 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>
<button id="load-more-users-btn" class="btn btn-primary">Load More</button>
</div>
<input type="hidden" template-variable="yourid" value="{yourid}" />

View File

@@ -1,21 +1,17 @@
<div class="container">
<ul class="breadcrumb">
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="/" itemprop="url"><span itemprop="title">Home</span></a>
<span class="divider">/</span>
</li>
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<span itemprop="title">{category_name}</span>
</li>
<div id="category_active_users"></div>
</ul>
</div>
<ol class="breadcrumb">
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
</li>
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<span itemprop="title">{category_name} <a target="_blank" href="../{category_id}.rss"><i class="icon-rss-sign"></i></a></span>
</li>
<div id="category_active_users"></div>
</ol>
<div>
<button id="new_post" class="btn btn-primary btn-large {show_topic_button}">New Topic</button>
<button id="new_post" class="btn btn-primary btn-lg {show_topic_button}">[[category:new_topic_button]]</button>
<div class="inline-block pull-right">
<a target="_blank" href="../{category_id}.rss"><i class="icon-rss-sign icon-2x"></i></a>&nbsp;
<a href="#" id="facebook-share"><i class="icon-facebook-sign icon-2x"></i></a>&nbsp;
<a href="#" id="twitter-intent"><i class="icon-twitter-sign icon-2x"></i></a>&nbsp;
<a href="#" id="google-share"><i class="icon-google-plus-sign icon-2x"></i></a>&nbsp;
@@ -25,71 +21,72 @@
<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?
[[category:no_topics]]
</div>
<div class="category row">
<div class="{topic_row_size}">
<ul id="topics-container">
<!-- BEGIN topics -->
<a href="../../topic/{topics.slug}"><li class="category-item {topics.deleted-class}">
<div class="row-fluid">
<!-- <div class="span1 thread-rating hidden-phone hidden-tablet">
<span>
<i class="icon-star icon-3x"></i><br />
38
</span>
</div> -->
<div class="span12 topic-row">
<div class="latest-post visible-desktop">
<div class="pull-right">
<img style="width: 48px; height: 48px; /*temporary*/" src="{topics.teaser_userpicture}" />
<p><strong>{topics.teaser_username}</strong>: {topics.teaser_text}</p>
<span>posted {topics.teaser_timestamp} ago</span>
<li class="category-item {topics.deleted-class}">
<div class="row">
<div class="col-md-12 topic-row">
<div class="latest-post visible-lg visible-md">
<a href="../../topic/{topics.slug}#{topics.teaser_pid}">
<div class="pull-right">
<img class="img-rounded" style="width: 48px; height: 48px; /*temporary*/" src="{topics.teaser_userpicture}" />
<p>{topics.teaser_text}</p>
<p class="meta">
<strong>{topics.teaser_username}</strong> posted <span class="timeago" title="{topics.teaser_timestamp}"></span>
</p>
</div>
</a>
</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>
<a href="../../topic/{topics.slug}">
<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 <span class="timeago" title="{topics.relativeTime}"></span> by
<strong>{topics.username}</strong>.
</small>
</div>
</a>
</div>
</div>
</div>
</li></a>
</li>
<!-- END topics -->
</ul>
</div>
<div class="span3 {show_sidebar} category-sidebar mobile-sidebar">
<div class="col-md-3 {show_sidebar} category-sidebar">
<div class="sidebar-block img-polaroid">
<div class="sidebar-block img-thumbnail">
<div class="block-header">
Recent Replies
[[category:sidebar.recent_replies]]
</div>
<div class="block-content recent-replies">
<ul id="category_recent_replies"></ul>
</div>
</div>
<div class="sidebar-block img-polaroid">
<div class="sidebar-block img-thumbnail">
<div class="block-header">
Active Participants
[[category:sidebar.active_participants]]
</div>
<div class="block-content">
<!-- BEGIN active_users -->
<a href="/users/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-polaroid" /></a>
<a href="/user/{active_users.userslug}"><img title="{active_users.username}" src="{active_users.picture}" class="img-rounded" /></a>
<!-- END active_users -->
</div>
</div>
<div class="sidebar-block img-polaroid {moderator_block_class}">
<div class="sidebar-block img-thumbnail {moderator_block_class}">
<div class="block-header">
Moderators
[[category:sidebar.moderators]]
</div>
<div class="block-content">
<!-- BEGIN moderators -->
<a href="/users/{moderators.userslug}"><img title="{moderators.username}" src="{moderators.picture}" class="img-polaroid" /></a>
<a href="/user/{moderators.userslug}"><img title="{moderators.username}" src="{moderators.picture}" class="img-rounded" /></a>
<!-- END moderators -->
</div>
</div>

View File

@@ -1,41 +1,42 @@
{
"custom_mapping": {
"admin/testing/categories[^]*": "admin/testing/categories",
"admin/topics[^]*": "admin/topics",
"admin/categories[^]*": "admin/categories",
"admin/users[^]*": "admin/users",
"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",
"admin/gplus[^]*": "admin/gplus",
"admin/testing/categories.*": "admin/testing/categories",
"admin/topics.*": "admin/topics",
"admin/categories.*": "admin/categories",
"admin/users.*": "admin/users",
"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",
"admin/gplus.*": "admin/gplus",
"admin/motd/?$": "admin/motd",
"admin/groups/?$": "admin/groups",
"install/?$": "install/mail",
"install/mail/?": "install/mail",
"install/social/?": "install/social",
"install/privileges/?": "install/privileges",
"users-sort-posts": "users",
"users-latest": "users",
"users-sort-reputation": "users",
"users-search": "users",
"users[^]*edit": "accountedit",
"users[^]*following": "following",
"users[^]*followers": "followers",
"users[^]*settings": "accountsettings",
"users[^]*favourites": "favourites",
"users/[^]*": "account",
"users/sort-posts": "users",
"users/latest": "users",
"users/sort-reputation": "users",
"users/search": "users",
"user.*edit": "accountedit",
"user.*following": "following",
"user.*followers": "followers",
"user.*settings": "accountsettings",
"user.*favourites": "favourites",
"user/.*": "account",
"recent": "recent",
"unread": "unread",
"popular": "category",
"active": "category",
"search": "search",
"reset/[^]*": "reset_code",
"reset/.*": "reset_code",
"reset": "reset"
},
"force_refresh": {
"logout": true

View File

@@ -1,34 +1,27 @@
<div class="well">
<div class="well favourites">
<div class="account-username-box" data-userslug="{userslug}">
<span class="account-username">
<a href="/users/{userslug}">{username}</a>
<a href="/user/{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">
<div class="row">
<div class="col-md-12 user-favourite-posts">
<!-- BEGIN posts -->
<div class="topic-row img-polaroid clearfix" topic-url="topic/{posts.tid}/#{posts.pid}">
<div class="topic-row img-thumbnail 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>
<span class="pull-right timeago" title="{posts.relativeTime}"></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>
<script type="text/javascript" src="{relative_path}/src/forum/favourites.js"></script>

View File

@@ -1,21 +1,21 @@
<div class="well">
<div class="well users">
<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>
<a href="/user/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/user/{userslug}/followers">followers</a>
</span>
</div>
<div>
<!-- BEGIN followers -->
<div class="users-box">
<a href="/users/{followers.userslug}">
<img src="{followers.picture}" class="img-polaroid"/>
<a href="/user/{followers.userslug}">
<img src="{followers.picture}" class="img-thumbnail"/>
</a>
<br/>
<a href="/users/{followers.userslug}">{followers.username}</a>
<a href="/user/{followers.userslug}">{followers.username}</a>
<br/>
<div title="reputation">
<span class='formatted-number'>{followers.reputation}</span>

View File

@@ -1,21 +1,21 @@
<div class="well">
<div class="well users">
<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>
<a href="/user/{userslug}">{username}</a> <i class="icon-chevron-right"></i>
<a href="/user/{userslug}/following">following</a>
</span>
</div>
<div>
<!-- BEGIN following -->
<div class="users-box">
<a href="/users/{following.userslug}">
<img src="{following.picture}" class="img-polaroid"/>
<a href="/user/{following.userslug}">
<img src="{following.picture}" class="img-thumbnail"/>
</a>
<br/>
<a href="/users/{following.userslug}">{following.username}</a>
<a href="/user/{following.userslug}">{following.username}</a>
<br/>
<div title="reputation">
<span class='formatted-number'>{following.reputation}</span>
@@ -25,7 +25,7 @@
<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}" data-username="{following.username}">Unfollow</a>
<a id="unfollow-btn" href="#" class="btn btn-default unfollow-btn" followingUid="{following.uid}" data-username="{following.username}">Unfollow</a>
</div>
<!-- END following -->
</div>

View File

@@ -2,54 +2,59 @@
</div><!--END container -->
<div id="disconnect-modal" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<h3 id="myModalLabel">Socket Disconnect</h3>
</div>
<div class="modal-body">
<span id="disconnect-text">Looks like you disconnected, try reloading the page.</span>
</div>
<div class="modal-footer">
<a id="reload-button" href="/" class="btn btn-primary">Reload</a>
</div>
</div>
<div id="chat-modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Chat" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="myModalLabel">[[footer:chat.chatting_with]]</h3>
</div>
<div class="modal-body">
<textarea class="form-control" id="chat-content" cols="40" rows="10" readonly></textarea><br/>
<input id="chat-message-input" type="text" class="form-control" name="chat-message" placeholder="[[footer:chat.placeholder]]"/>
</div>
<div class="modal-footer">
<button type="button" id="chat-message-send-btn" href="#" class="btn btn-primary btn-lg btn-block
">[[footer:chat.send]]</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div id="chat-modal" class="modal hide" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="myModalLabel">Chat with <span id="chat-with-name"></span></h3>
</div>
<div class="modal-body">
<textarea id="chat-content" cols="40" rows="10" readonly></textarea><br/>
<input id="chat-message-input" type="text" name="chat-message" placeholder="type chat message here press enter to send"/><br/>
<button type="button" id="chat-message-send-btn" href="#" class="btn btn-primary">Send</button>
</div>
</div>
<div id="alert_window"></div>
<div id="mobile-sidebar">
</div>
<!-- START Forum Info -->
<div id="footer" class="container hidden-phone" style="padding-top: 50px; display: none">
<div class="alert alert-info">
<span id="active_users"></span>; <span id="active_record"></span><br />
<span id="number_of_users"></span> <span id="post_stats"></span><br />
<span id="latest_user"></span>
<footer id="footer" class="container footer">
<div class="row footer-stats">
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_online"></span><br /><small>[[footer:stats.online]]</small></h2>
</div>
</div>
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_users"></span><br /><small>[[footer:stats.users]]</small></h2>
</div>
</div>
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_topics"></span><br /><small>[[footer:stats.topics]]</small></h2>
</div>
</div>
<div class="col-md-3 col-xs-6">
<div class="stats-card well">
<h2><span id="stats_posts"></span><br /><small>[[footer:stats.posts]]</small></h2>
</div>
</div>
</div>
<footer class="footer">Copyright &copy; 2013 <a target="_blank" href="http://www.nodebb.org">NodeBB</a> by <a target="_blank" href="https://github.com/psychobunny">psychobunny</a>, <a href="https://github.com/julianlam" target="_blank">julianlam</a>, <a href="https://github.com/barisusakli" target="_blank">barisusakli</a> from <a target="_blank" href="http://www.designcreateplay.com">designcreateplay</a></footer>
</div>
<div id="mobile-menu">
<button id="mobile-menu-btn" type="button" class="btn btn-none"><i class="icon-th icon-2x"></i></button>
<button id="mobile-post-btn" type="button" class="btn btn-none"><i class="icon-plus icon-2x"></i></button>
</div>
<!-- END Forum Info -->
<div class="copyright">Copyright &copy; 2013 <a target="_blank" href="http://www.nodebb.org">NodeBB</a> by <a target="_blank" href="https://github.com/psychobunny">psychobunny</a>, <a href="https://github.com/julianlam" target="_blank">julianlam</a>, <a href="https://github.com/barisusakli" target="_blank">barisusakli</a> from <a target="_blank" href="http://www.designcreateplay.com">designcreateplay</a></div>
</footer>
<script>
$.getScript(RELATIVE_PATH + '/src/forum/footer.js');
</script>
</body>
</html>

View File

@@ -4,79 +4,114 @@
<title>{browserTitle}</title>
{meta_tags}
<link href="{cssSrc}" rel="stylesheet" media="screen">
<link href="{relative_path}/vendor/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen">
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
<script>
var RELATIVE_PATH = "{relative_path}";
</script>
<script 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>
<!-- BEGIN clientScripts -->
<script src="{relative_path}/{clientScripts.script}"></script>
<!-- END clientScripts -->
<script>
require.config({
baseUrl: "{relative_path}/src/modules",
waitSeconds: 3
});
</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>
<div id="mobile-menu-overlay">
</div>
<div class="navbar navbar-inverse navbar-fixed-top" id="header-menu">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="/">{title}</a>
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<div class="navbar navbar-inverse navbar-fixed-top header" role="navigation" id="header-menu">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="nav-collapse collapse">
<ul id="main-nav" class="nav nodebb-inline-block">
<li>
<a href="/recent">Recent</a>
</li>
<li class="nodebb-loggedin">
<a href="/unread"><span id="numUnreadBadge" class="badge badge-inverse">0</span> Unread</a>
</li>
<li>
<a href="/users">Users</a>
</li>
</ul>
<ul id="right-menu" class="nav pull-right nodebb-inline-block">
<li class="notifications dropdown text-center">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="notif_dropdown"><i class="icon-circle-blank"></i></a>
<ul id="notif-list" class="dropdown-menu" aria-labelledby="notif_dropdown">
<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>
<a href="/">
<h1 class="navbar-brand forum-title">{title}</h1>
</a>
</div>
<div class="navbar-collapse collapse navbar-ex1-collapse">
<ul id="main-nav" class="nav navbar-nav">
<li>
<a href="/recent">[[global:header.recent]]</a>
</li>
<li class="nodebb-loggedin">
<a href="/unread"><span id="numUnreadBadge" class="badge badge-inverse">0</span> [[global:header.unread]]</a>
</li>
<li>
<a href="/users">[[global:header.users]]</a>
</li>
<li class="visible-xs">
<a href="/search">[[global:header.search]]</a>
</li>
<li class="visible-xs">
<a href="/search">Search</a>
</li>
<li>
<a href="/"></a>
</li>
</ul>
<form id="search-form" class="navbar-form navbar-right hidden-xs" role="search" method="GET" action="">
<div class="hide" id="search-fields">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" name="query" value="">
</div>
<button type="submit" class="btn btn-default hide">[[global:search]]</button>
</div>
<button id="search-button" type="button" class="btn btn-link"><i class="icon-search"></i></button>
</form>
<ul id="logged-in-menu" class="nav navbar-nav navbar-right hide">
<li>
<a href="#" id="reconnect"></a>
</li>
<li id="notifications-list" class="notifications dropdown text-center hidden-xs">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="notif_dropdown"><i class="icon-circle-blank"></i></a>
<ul id="notif-list" class="dropdown-menu" aria-labelledby="notif_dropdown">
<li>
<a href="#"><i class="icon-refresh icon-spin"></i> [[global:notifications.loading]]</a>
</li>
</ul>
</li>
<li>
<a id="user_label" href="">
<img src=""/>
<span></span>
</a>
</li>
<li id="logout-link">
<a href="#">Log out</a>
</li>
</ul>
<ul id="logged-out-menu" class="nav navbar-nav navbar-right">
<li id="register-link">
<a href="/register">Register</a>
</li>
<li id="login-link">
<a href="/login">Login</a>
</li>
</ul>
<div class="pagination-block">
<i class="icon-upload pointer"></i>
<span id="pagination"></span>
<i class="icon-upload pointer icon-rotate-180"></i>
</div>
</div>
</div>
</div>
<input id="csrf_token" type="hidden" template-variable="csrf" value="{csrf}" />
<div class="container" id="content">
<div class="container" id="content">

View File

@@ -1,13 +1,13 @@
<div class="hero-unit {motd_class}">
<div class="jumbotron {motd_class}">
{motd}
</div>
<div class="row category-row">
<div class="row home">
<!-- BEGIN categories -->
<div class="span3">
<div class="col-md-3 col-xs-6">
<a href="category/{categories.slug}">
<h4><span class="badge {categories.badgeclass}">{categories.topic_count} </span> {categories.name}</h4>
<div class="category-icon {categories.blockclass}">
<div class="icon {categories.blockclass}">
<div id="category-{categories.cid}" class="category-slider-{categories.post_count}">
<div class="category-box"><i class="{categories.icon} icon-4x"></i></div>
<div class="category-box">{categories.description}</div>

View File

@@ -30,10 +30,10 @@
<hr />
<div class="pull-right">
<button data-path="mail" class="btn btn-primary btn-large">Next &ndash; <i class="icon-envelope"></i> Mail</button>
<button data-path="mail" class="btn btn-primary btn-lg">Next &ndash; <i class="icon-envelope"></i> Mail</button>
</div>
<div>
<button data-path="redis" class="btn btn-primary btn-large">Previous &ndash; <i class="icon-hdd"></i> Redis</button>
<button data-path="redis" class="btn btn-primary btn-lg">Previous &ndash; <i class="icon-hdd"></i> Redis</button>
</div>
<script>

View File

@@ -9,7 +9,7 @@
// ajaxify.go('install/redis');
// app.alert({
// alert_id: 'config-ready',
// type: 'error',
// type: 'danger',
// timeout: 10000,
// title: 'NodeBB Configuration Not Ready!',
// message: 'NodeBB cannot proceed with setup at this time as Redis database information ' +
@@ -134,7 +134,7 @@
timeout: 2500,
title: 'Configuration Not Saved',
message: 'NodeBB encountered a problem saving your changes',
type: 'error'
type: 'danger'
});
}
});

View File

@@ -26,7 +26,7 @@
<div class="navbar-inner">
<div class="container">
<a class="brand" href="/install">NodeBB Installation</a>
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<button type="button" class="btn navbar-btn" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>

View File

@@ -23,7 +23,7 @@
<hr />
<div class="pull-right">
<button data-path="social" class="btn btn-primary btn-large">Next &ndash; <i class="icon-facebook"></i> Social</button>
<button data-path="social" class="btn btn-primary btn-lg">Next &ndash; <i class="icon-facebook"></i> Social</button>
</div>
<script>

View File

@@ -27,10 +27,10 @@
<hr />
<div class="pull-right">
<button id="start-nodebb" class="btn btn-success btn-large"><i class="icon-thumbs-up"></i> Start using NodeBB!</button>
<button id="start-nodebb" class="btn btn-success btn-lg"><i class="icon-thumbs-up"></i> Start using NodeBB!</button>
</div>
<div>
<button data-path="social" class="btn btn-primary btn-large">Previous &ndash; <i class="icon-facebook"></i> Social</button>
<button data-path="social" class="btn btn-primary btn-lg">Previous &ndash; <i class="icon-facebook"></i> Social</button>
</div>
<script>

View File

@@ -32,7 +32,7 @@
<hr />
<div class="pull-right">
<button data-path="basic" class="btn btn-primary btn-large" disabled>Next &ndash; <i class="icon-cog"></i> Basic</button>
<button data-path="basic" class="btn btn-primary btn-lg" disabled>Next &ndash; <i class="icon-cog"></i> Basic</button>
</div>
<script>

View File

@@ -34,10 +34,10 @@
<hr />
<div class="pull-right">
<button data-path="privileges" class="btn btn-primary btn-large">Next &ndash; <i class="icon-legal"></i> Privileges</button>
<button data-path="privileges" class="btn btn-primary btn-lg">Next &ndash; <i class="icon-legal"></i> Privileges</button>
</div>
<div>
<button data-path="mail" class="btn btn-primary btn-large">Previous &ndash; <i class="icon-envelope"></i> Mail</button>
<button data-path="mail" class="btn btn-primary btn-lg">Previous &ndash; <i class="icon-envelope"></i> Mail</button>
</div>
<script>

View File

@@ -1,29 +1,63 @@
<h1>Login</h1>
<div class="row-fluid">
<div class="well {login_window:spansize}">
<h4>Login via Username &amp; Password</h4>
<div class="alert alert-error" id="error" style="display:none">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>Failed Login Attempt</strong> <p></p>
<ol class="breadcrumb">
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
</li>
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<span itemprop="title">[[login:login]]</span>
</li>
</ol>
<div class="row">
<div class="{login_window:spansize}">
<div class="well well-lg">
<div class="alert alert-danger" id="login-error-notify" style="display:none">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>[[login:failed_login_attempt]]</strong> <p></p>
</div>
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="username" class="col-lg-2 control-label">[[login:username]]</label>
<div class="col-lg-10">
<input class="form-control" type="text" placeholder="Enter Username" name="username" id="username" />
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">[[login:password]]</label>
<div class="col-lg-10">
<input class="form-control" type="password" placeholder="Enter Password" name="password" id="password" />
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<div class="checkbox">
<label>
<input type="checkbox"> [[login:remember_me]]
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button class="btn btn-primary" id="login" type="submit">[[login:login]]</button> &nbsp; <a href="/reset">[[login:forgot_password]]</a>
</div>
</div>
<input type="hidden" name="_csrf" value="{token}" id="csrf-token" />
</form>
</div>
<form>
<label>Username</label><input type="text" placeholder="Enter Username" name="username" id="username" /><br />
<label>Password</label><input type="password" placeholder="Enter Password" name="password" id="password" /><br />
<input type="hidden" name="_csrf" value="{token}" id="csrf-token" />
<button class="btn btn-primary" id="login" type="submit">Login</button> &nbsp; <a href="/reset">Forgot Password?</a>
</form>
<div id="login-error-notify" class="alert alert-danger hide">Invalid username/password</div>
</div>
<div class="well span6 {alternate_logins:display}">
<h4>Alternative Logins</h4>
<ul class="alt-logins">
<li data-url="/auth/twitter" class="twitter {twitter:display}"><i class="icon-twitter-sign icon-3x"></i></li>
<li data-url="/auth/google" class="google {google:display}"><i class="icon-google-plus-sign icon-3x"></i></li>
<li data-url="/auth/facebook" class="facebook {facebook:display}"><i class="icon-facebook-sign icon-3x"></i></li>
</ul>
<div class="col-md-6 {alternate_logins:display}">
<div class="well well-lg">
<h4>[[login:alternative_logins]]</h4>
<ul class="alt-logins">
<li data-url="/auth/twitter" class="twitter {twitter:display}"><i class="icon-twitter-sign icon-3x"></i></li>
<li data-url="/auth/google" class="google {google:display}"><i class="icon-google-plus-sign icon-3x"></i></li>
<li data-url="/auth/facebook" class="facebook {facebook:display}"><i class="icon-facebook-sign icon-3x"></i></li>
</ul>
</div>
</div>
</div>

View File

@@ -1,7 +1,13 @@
<div class="alert" id="message">
<strong>Logout</strong>
<p>You have successfully logged out of NodeBB</p>
<p>
<a href="/">NodeBB Home</a>
</p>
<ol class="breadcrumb">
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
</li>
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<span itemprop="title">[[global:logout]]</span>
</li>
</ol>
<div class="alert alert-success" id="message">
<h4>[[global:logout.title]]</h4>
<p>[[global:logout.message]]</p>
</div>

View File

@@ -1,17 +1,19 @@
<ul class="breadcrumb">
<li><a href="/">Home</a><span class="divider">/</span></li>
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li class="active">{category_name}</li>
<div id="category_active_users"></div>
</ul>
</ol>
<ul class="topics">
<!-- BEGIN topics -->
<li>
<span class="timestamp">{topics.teaser_timestamp}</span>
<a href="../../topic/{topics.slug}">{topics.title} ({topics.postcount})</a>
<div class="teaser">
<img class="img-polaroid" src="../../graph/users/{topics.teaser_username}/picture" />
<img class="img-thumbnail" src="{topics.teaser_userpicture}" />
<p>
{topics.teaser_text} &mdash; {topics.teaser_timestamp} ago
{topics.teaser_text}
</p>
<div class="clear"></div>
</div>
</li>
<!-- END topics -->

View File

@@ -1,6 +1,6 @@
<div class="alert alert-error">
<div class="alert alert-danger">
<p>
Your browser does not seem to support javascript. As a result, your viewing experience will be diminished.
Your browser does not seem to support javascript. As a result, your viewing experience will be diminished, and you have been placed in <strong>read-only mode</strong>.
</p>
<p>
Please download a browser that supports javascript, or enable it, if it disabled (i.e. NoScript).

View File

@@ -1,12 +1,13 @@
<ul class="posts">
<!-- BEGIN main_posts -->
<li>
<div class="row-fluid">
<div class="span2">
<img src="{main_posts.picture}" /><br />
{main_posts.username}
<a name="{main_posts.pid}"></a>
<div class="row">
<div class="col-lg-2 profile">
<img class="img-thumbnail" src="{main_posts.picture}" /><br />
<span class="username">{main_posts.username}</span>
</div>
<div class="span10">
<div class="col-lg-10">
{main_posts.content}
</div>
</div>
@@ -14,12 +15,13 @@
<!-- END main_posts -->
<!-- BEGIN posts -->
<li>
<div class="row-fluid">
<div class="span2">
<img src="{posts.picture}" /><br />
{posts.username}
<a name="{posts.pid}"></a>
<div class="row">
<div class="col-lg-2 profile">
<img class="img-thumbnail" src="{posts.picture}" /><br />
<span class="username">{posts.username}</span>
</div>
<div class="span10">
<div class="col-lg-10">
{posts.content}
</div>
<div class="clear"></div>

View File

@@ -1,11 +1,27 @@
<div class="hero-unit">
<h2 class="form-signin-heading">Now Leaving NodeBB</h2>
<p>
You are now leaving NodeBB.
</p>
<br />
<p>
<a href="{url}" rel="nofollow" class="btn btn-large">Continue to {url}</a>
<a href="{home}" class="btn btn-large btn-inverse">Return to NodeBB</a>
</p>
</div>
<div class="outgoing">
<ol class="breadcrumb">
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="/" itemprop="url"><span itemprop="title">Home</span></a>
</li>
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
<span itemprop="title">Outgoing Link</span>
</li>
</ol>
<div class="well">
<h3>
You are now leaving NodeBB.
</h3>
<p>
<a href="{url}" rel="nofollow" class="btn btn-primary btn-lg">Continue to {url}</a>
<a id="return-btn" href="#" class="btn btn-lg btn-warning">Return to NodeBB</a>
</p>
</div>
</div>
<script>
$('#return-btn').on('click', function() {
history.back();
return false;
});
</script>

View File

@@ -1,11 +1,8 @@
<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>
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li class="active">{category_name}</li>
<div id="category_active_users"></div>
</ol>
<a href="/recent">
<div class="alert hide" id="new-topics-alert"></div>
@@ -21,23 +18,29 @@
<!-- 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 class="row">
<div class="col-md-12 col-xs-12 topic-row img-thumbnail">
<div class="latest-post visible-lg visible-md">
<a href="../../topic/{topics.slug}#{topics.teaser_pid}">
<div class="pull-right">
<img class="img-rounded" style="width: 48px; height: 48px; /*temporary*/" src="{topics.teaser_userpicture}" />
<p>{topics.teaser_text}</p>
<p class="meta">
<strong>{topics.teaser_username}</strong> posted <span class="timeago" title="{topics.teaser_timestamp}"></span>
</p>
</div>
</a>
</div>
<a href="../../topic/{topics.slug}">
<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 <span class="timeago" title="{topics.relativeTime}"></span> by
<strong>{topics.username}</strong>.
</small>
</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>
</a>
</div>
</div>
</li>
@@ -47,4 +50,4 @@
</div>
</div>
<script type="text/javascript" src="{relative_path}/src/forum/recent.js"></script>
<script type="text/javascript" src="{relative_path}/src/forum/recent.js"></script>

Some files were not shown because too many files have changed in this diff Show More