diff --git a/.gitignore b/.gitignore index 49f144eb0a..b5bda3e26f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ npm-debug.log node_modules/ sftp-config.json config.json +public/src/nodebb.min.js public/config.json public/css/*.css public/themes/* @@ -15,4 +16,4 @@ public/themes/* *.sublime-project *.sublime-workspace plugins/* -.project \ No newline at end of file +.project diff --git a/README.md b/README.md index 4e55deda6c..f1f90f2a98 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -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 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 Main Category Listing](http://i.imgur.com/ZBrHqLr.png) ![NodeBB Topic Page](http://i.imgur.com/YSBA6Vr.png) diff --git a/app.js b/app.js index 7b9196361c..343431e527 100644 --- a/app.js +++ b/app.js @@ -24,8 +24,10 @@ nconf.argv().env(); var fs = require('fs'), + async = require('async'), winston = require('winston'), pkg = require('./package.json'), + path = require('path'), meta; // Runtime environment diff --git a/package.json b/package.json index d34cef3a13..b82a2747be 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "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", @@ -37,7 +37,9 @@ "nodebb-plugin-mentions": "~0.1.0", "nodebb-plugin-markdown": "~0.1.0", "rss": "~0.2.0", - "prompt": "~0.2.11" + "prompt": "~0.2.11", + "uglify-js": "~2.4.0", + "validator": "~1.5.1" }, "bugs": { "url": "https://github.com/designcreateplay/NodeBB/issues" diff --git a/public/language/en/users.json b/public/language/en/users.json index c41127ad9d..0d86d2f19c 100644 --- a/public/language/en/users.json +++ b/public/language/en/users.json @@ -2,6 +2,7 @@ "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" diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index e42e927274..38300a481d 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -62,9 +62,11 @@ 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); @@ -105,26 +107,29 @@ var ajaxify = {}; // Enhancing all anchors to ajaxify... $(document.body).on('click', 'a', function (e) { - function hrefEmpty(href) { return href == 'javascript:;' || href == window.location.href + "#" || href.slice(-1) === "#"; } if (hrefEmpty(this.href)) return; - - var url = this.href.replace(rootUrl + '/', ''); - if (this.target !== '') return; + if (this.protocol === 'javascript:') return; if (!e.ctrlKey && e.which === 1) { - if (ajaxify.go(url)) { + if (this.host === window.location.host) { + var url = this.href.replace(rootUrl + '/', ''); + + if (ajaxify.go(url)) { + e.preventDefault(); + } + } else { + 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 @@ -156,7 +161,7 @@ var ajaxify = {}; var scripts = [], script, - children_nodes = body_el.childNodes, + children_nodes = $(body_el).children(), child, i; diff --git a/public/src/app.js b/public/src/app.js index 950b3b6d44..f9a793c055 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -7,7 +7,7 @@ var socket, (function () { var showWelcomeMessage = false; - function loadConfig() { + app.loadConfig = function() { $.ajax({ url: RELATIVE_PATH + '/api/config', @@ -15,109 +15,114 @@ var socket, API_URL = data.api_url; config = data; - socket = io.connect(config.socket.address); + if(socket) { + socket.disconnect(); + socket.socket.connect(); + } else { + socket = io.connect(config.socket.address); - var reconnecting = false; - var reconnectTries = 0; + var reconnecting = false; + var reconnectTries = 0; - socket.on('event:connect', function (data) { - console.log('connected to nodebb socket: ', data); - app.username = data.username; - app.showLoginMessage(); - }); - - socket.on('event:alert', function (data) { - app.alert(data); - }); - - socket.on('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.on('event:connect', function (data) { + app.username = data.username; + app.showLoginMessage(); socket.emit('api:updateHeader', { - fields: ['username', 'picture', 'userslug'] - }); - } - }); - - socket.on('reconnecting', function (data) { - function showDisconnectModal() { - $('#disconnect-modal').modal({ - backdrop: 'static', - show: true - }); - - $('#reload-button').on('click', function () { - $('#disconnect-modal').modal('hide'); - window.location.reload(); - }); - } - - reconnecting = true; - reconnectTries++; - - if (reconnectTries > 4) { - showDisconnectModal(); - return; - } - - app.alert({ - alert_id: 'connection_alert', - title: 'Reconnecting', - message: 'You have disconnected from NodeBB, we will try to reconnect you.
', - type: 'warning', - timeout: 5000 + fields: ['username', 'picture', 'userslug'] + }); }); - }); - socket.on('api:user.get_online_users', function (users) { - jQuery('a.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) { + 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'] + }); + } + }); - if (uid && jQuery.inArray(uid, users) !== -1) { - el.find('i').remove(); - el.prepend(''); - } else { - el.find('i').remove(); - el.prepend(''); + socket.on('reconnecting', function (data) { + function showDisconnectModal() { + $('#disconnect-modal').modal({ + backdrop: 'static', + show: true + }); + + $('#reload-button').on('click', function () { + $('#disconnect-modal').modal('hide'); + window.location.reload(); + }); } - el.processed = true; - }); - jQuery('button .username-field').each(function () { - //DRY FAIL - if (this.processed === true) + reconnecting = true; + reconnectTries++; + + if (reconnectTries > 4) { + showDisconnectModal(); 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.alert({ + alert_id: 'connection_alert', + title: 'Reconnecting', + message: 'You have disconnected from NodeBB, we will try to reconnect you.
', + type: 'warning', + timeout: 5000 + }); }); - }); - app.enter_room('global'); + 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(''); + } else { + el.find('i').remove(); + el.prepend(''); + } + + 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 }); @@ -275,7 +280,7 @@ var socket, if (active) { jQuery('#main-nav li a').each(function () { var href = this.getAttribute('href'); - if (active == "sort-posts" || active == "sort-reputation" || active == "search" || active == "latest") + 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'); @@ -286,6 +291,7 @@ var socket, $('span.timeago').timeago(); + setTimeout(function () { window.scrollTo(0, 1); // rehide address bar on mobile after page load completes. }, 100); @@ -402,7 +408,8 @@ 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) { @@ -427,19 +434,16 @@ var socket, } jQuery('document').ready(function () { - translator.load('global'); - $('#search-form').on('submit', function () { var input = $(this).find('input'); ajaxify.go("search/" + input.val(), null, "search"); input.val(''); return false; - }) + }); }); showWelcomeMessage = location.href.indexOf('loggedin') !== -1; - loadConfig(); - + app.loadConfig(); }()); \ No newline at end of file diff --git a/public/src/forum/accountedit.js b/public/src/forum/accountedit.js index 527eed2180..25c6018e91 100644 --- a/public/src/forum/accountedit.js +++ b/public/src/forum/accountedit.js @@ -27,6 +27,7 @@ $(document).ready(function() { }, uploadProgress: function(event, position, total, percent) { + console.log(percent); $('#upload-progress-bar').css('width', percent + '%'); }, @@ -60,25 +61,25 @@ $(document).ready(function() { }); 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) { diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index 254da9a2ce..3f91308384 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -26,7 +26,6 @@ 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(); @@ -59,6 +58,7 @@ if (data['username']) userLabel.find('span').html(data['username']); } else { + rightMenu.empty(); var userli = $('
  • \ \ \ @@ -67,7 +67,16 @@
  • '); rightMenu.append(userli); - var logoutli = $('
  • Log out
  • '); + var logoutli = $('
  • Log out
  • '); + logoutli.on('click', function() { + var csrf_token = $('#csrf_token').val(); + + $.post(RELATIVE_PATH + '/logout', { + _csrf: csrf_token + }, function() { + window.location = RELATIVE_PATH + '/'; + }); + }); rightMenu.append(logoutli); } } else { @@ -86,6 +95,11 @@ 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 diff --git a/public/src/forum/login.js b/public/src/forum/login.js index 5ff7a2de58..0fbf475521 100644 --- a/public/src/forum/login.js +++ b/public/src/forum/login.js @@ -32,7 +32,12 @@ $('#login-error-notify').show(); } else { $('#login-error-notify').hide(); - window.location.replace(RELATIVE_PATH + "/?loggedin"); + //window.location.replace(RELATIVE_PATH + "/?loggedin"); + history.go(-1); + //setTimeout(function(){ + app.loadConfig(); + //}, 500); + //socket.emit('api:updateHeader'); } }, error: function(data, textStatus, jqXHR) { diff --git a/public/src/forum/search.js b/public/src/forum/search.js index 5044a9be76..f7238ef8c3 100644 --- a/public/src/forum/search.js +++ b/public/src/forum/search.js @@ -12,6 +12,13 @@ $('#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; + }); }); })(); \ No newline at end of file diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 8f960577bd..246d25a409 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -215,19 +215,30 @@ }, false); } - $(window).off('scroll').on('scroll', function() { - var bottom = ($(document).height() - $(window).height()) * 0.9; + enableInfiniteLoading(); - if ($(window).scrollTop() > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) { - app.loadMorePosts(tid); - } - }); + 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; + + if ($(window).scrollTop() > bottom && !app.infiniteLoaderActive && $('#post-container').children().length) { + app.loadMorePosts(tid); + console.log('window scrolling'); + } + }); + } + var reply_fn = function() { var selectionText = '', selection = window.getSelection() || document.getSelection(); @@ -659,6 +670,7 @@ 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; @@ -666,7 +678,7 @@ } - var count = 0; + var count = 0, smallestNonNegative = 0; jQuery('.sub-posts').each(function() { count++; @@ -682,6 +694,11 @@ 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(); diff --git a/public/src/forum/users.js b/public/src/forum/users.js index bf69cdad9f..a5d29adaa7 100644 --- a/public/src/forum/users.js +++ b/public/src/forum/users.js @@ -74,7 +74,12 @@ }); - + 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({ @@ -91,24 +96,32 @@ set = 'users:postcount'; } 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; - }); + 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() { diff --git a/public/src/templates.js b/public/src/templates.js index 0528ce67dd..9d92f8bdda 100644 --- a/public/src/templates.js +++ b/public/src/templates.js @@ -180,7 +180,6 @@ else template_data['relative_path'] = RELATIVE_PATH; - translator.translate(templates[tpl_url].parse(template_data), function (translatedTemplate) { document.getElementById('content').innerHTML = translatedTemplate; }); @@ -233,7 +232,7 @@ } function makeRegex(block) { - return new RegExp("[^]*", 'g'); + return new RegExp("[\\s\\S]*", 'g'); } function getBlock(regex, block, template) { @@ -302,7 +301,7 @@ } if (namespace) { - var regex = new RegExp("{" + namespace + "[^]*?}", 'g'); + var regex = new RegExp("{" + namespace + "[\\s\\S]*?}", 'g'); template = template.replace(regex, ''); } diff --git a/public/templates/accountedit.tpl b/public/templates/accountedit.tpl index bb0c303bbd..36c0238e48 100644 --- a/public/templates/accountedit.tpl +++ b/public/templates/accountedit.tpl @@ -48,13 +48,15 @@ -
    -
    +
    +
    + success +
    -
    +
    -
    +
    diff --git a/public/templates/config.json b/public/templates/config.json index c478149352..f97cd5ce26 100644 --- a/public/templates/config.json +++ b/public/templates/config.json @@ -1,40 +1,40 @@ { "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", - "user[^]*edit": "accountedit", - "user[^]*following": "following", - "user[^]*followers": "followers", - "user[^]*settings": "accountsettings", - "user[^]*favourites": "favourites", - "user/[^]*": "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" }, diff --git a/public/templates/header.tpl b/public/templates/header.tpl index 9f172b30a6..fc1a8f1130 100644 --- a/public/templates/header.tpl +++ b/public/templates/header.tpl @@ -8,28 +8,18 @@ - - - - - - - + + + - - - - - - @@ -37,12 +27,14 @@
    + + + + + +

    {title}

    +
    +