diff --git a/public/src/app.js b/public/src/app.js index 7c78e6e26f..bc583e2b17 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -454,6 +454,7 @@ var socket, $('#search-fields').hide(); $('#search-button').show(); }); + return false; }); var loggedInMenu = $('#logged-in-menu'), @@ -501,7 +502,7 @@ var socket, } - $('#main-nav a,#user-control-list a,#logged-out-menu li a').off('click').on('click', function() { + $('#main-nav a,#user-control-list a,#logged-out-menu li a,#logged-in-menu .visible-xs').off('click').on('click', function() { if($('.navbar .navbar-collapse').hasClass('in')) { $('.navbar-header button').click(); } diff --git a/public/src/forum/account.js b/public/src/forum/account.js index d3c639cf66..e3c3ec7fe5 100644 --- a/public/src/forum/account.js +++ b/public/src/forum/account.js @@ -68,10 +68,6 @@ define(['forum/accountheader'], function(header) { app.openChat(username, theirid); }); - $('.user-recent-posts .topic-row').on('click', function() { - ajaxify.go($(this).attr('topic-url')); - }); - socket.on('user.isOnline', Account.handleUserOnline); socket.emit('user.isOnline', theirid, Account.handleUserOnline); diff --git a/public/src/forum/admin/themes.js b/public/src/forum/admin/themes.js index 7c0baaa568..a3685e127e 100644 --- a/public/src/forum/admin/themes.js +++ b/public/src/forum/admin/themes.js @@ -11,12 +11,6 @@ define(function() { themeEvent = function(e) { if (e.target.hasAttribute('data-action')) { switch (e.target.getAttribute('data-action')) { - case 'preview': - var cssSrc = $(e.target).parents('li').attr('data-css'), - cssEl = document.getElementById('base-theme'); - - cssEl.href = cssSrc; - break; case 'use': var parentEl = $(e.target).parents('li'), themeType = parentEl.attr('data-type'), @@ -40,6 +34,7 @@ define(function() { } } }; + bootstrapThemeContainer.addEventListener('click', themeEvent); installedThemeContainer.addEventListener('click', themeEvent); @@ -81,7 +76,6 @@ define(function() { '
' + '
' + ' ' + - '' + '
' + '

' + themes[x].name + '

' + '

' + @@ -120,7 +114,6 @@ define(function() { '

' + '
' + ' ' + - '' + '
' + '

' + theme.name + '

' + '

' + theme.description + '

' + diff --git a/public/src/forum/category.js b/public/src/forum/category.js index e3a8688472..a1b5338eda 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -153,13 +153,14 @@ define(['composer'], function(composer) { var replies = ''; for (var i = 0, numPosts = posts.length; i < numPosts; ++i) { - replies += '
  • ' + + replies += '
  • ' + '' + - '' + - ''+ posts[i].username + '' + - '

    ' + posts[i].content + '

    ' + - '
    ' + - '' + + ''+ posts[i].username + '' + + '

    ' + posts[i].content + '

    ' + + ''+ + 'posted '+ + '' + + ''+ '
  • '; } diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index 1bcdac57bf..d5eac0bf4f 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -8,11 +8,17 @@ define(['notifications', 'chat'], function(Notifications, Chat) { Chat.prepareDOM(); translator.prepareDOM(); - function updateUnreadCount(err, count) { + function updateUnreadCount(err, tids) { + var count = 0; + if(tids && tids.length) { + count = tids.length; + } + $('#unread-count').toggleClass('unread-count', count > 0); $('#unread-count').attr('data-content', count > 20 ? '20+' : count); } + socket.on('event:unread.updateCount', updateUnreadCount); socket.emit('user.getUnreadCount', updateUnreadCount); }); \ No newline at end of file diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 90e5ea0cb0..e0aa241e2b 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -522,8 +522,10 @@ define(['composer'], function(composer) { $('#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 post = $(this).parents('li.post-row'), + username = post.attr('data-username'), + touid = post.attr('data-uid'); + app.openChat(username, touid); $(this).parents('.btn-group').find('.dropdown-toggle').click(); return false; @@ -537,7 +539,6 @@ define(['composer'], function(composer) { 'posts.favourite' ]); - socket.on('get_users_in_room', function(data) { if(data && data.room.indexOf('topic') !== -1) { @@ -641,6 +642,8 @@ define(['composer'], function(composer) { } } + socket.emit('topics.markAsRead', {tid: tid, uid: app.uid}); + createNewPosts(data); }); diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index a930cd509c..966afa6ae0 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -264,6 +264,7 @@ define(['taskbar'], function(taskbar) { newHeight = $(window).height() - $('#header-menu').height() - 20; } postContainer.css('height', newHeight); + $('body').css({'margin-bottom': newHeight}); resizeSavePosition(newHeight); } e.preventDefault(); @@ -353,6 +354,7 @@ define(['taskbar'], function(taskbar) { postContainer.css('visibility', 'visible') .css('z-index', 1); + $('body').css({'margin-bottom': postContainer.css('height')}); composer.focusElements(post_uuid); } @@ -436,6 +438,7 @@ define(['taskbar'], function(taskbar) { delete composer.posts[post_uuid]; composer.active = undefined; taskbar.discard('composer', post_uuid); + $('body').css({'margin-bottom': 0}); } } diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 8994ba008a..2fff715165 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -21,7 +21,7 @@ define(function() { if (!err && (data.read.length + data.unread.length) > 0) { for (x = 0; x < numUnread; x++) { notifEl.setAttribute('data-nid', data.unread[x].nid); - notifEl.className = 'unread'; + notifEl.className = data.unread[x].readClass; notifEl.innerHTML = '' + utils.relativeTime(data.unread[x].datetime, true) + '' + data.unread[x].text + ''; notifFrag.appendChild(notifEl.cloneNode(true)); } diff --git a/public/src/utils.js b/public/src/utils.js index 9f987b5ef0..bc97257382 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -99,17 +99,27 @@ return difference + (min ? 'y' : ' year') + (difference !== 1 && !min ? 's' : ''); }, + invalidUnicodeChars : XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'), + invalidLatinChars : /[^\w\s\d\-_]/g, + + trimRegex : /^\s+|\s+$/g, + collapseWhitespace : /\s+/g, + collapseDash : /-+/g, + trimTrailingDash : /-$/g, + isLatin : /^[\w]+$/, + //http://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/ slugify: function(str) { - var invalidChars = XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'); - - str = str.replace(/^\s+|\s+$/g, ''); // trim + str = str.replace(utils.trimRegex, ''); str = str.toLowerCase(); - str = XRegExp.replace(str, invalidChars, '-'); - str = str.replace(/\s+/g, '-') // collapse whitespace and replace by - - str = str.replace(/-+/g, '-'); // collapse dashes - str = str.replace(/-$/g, ''); - + if(utils.isLatin.test(str)) { + str = str.replace(utils.invalidLatinChars, '-'); + } else { + str = XRegExp.replace(str, utils.invalidUnicodeChars, '-'); + } + str = str.replace(utils.collapseWhitespace, '-') + str = str.replace(utils.collapseDash, '-'); + str = str.replace(utils.trimTrailingDash, ''); return str; }, @@ -202,7 +212,7 @@ if (!String.prototype.trim) { String.prototype.trim = function() { - return this.replace(/^\s+|\s+$/g, ''); + return this.replace(utils.trimRegex, ''); }; } diff --git a/public/templates/account.tpl b/public/templates/account.tpl index 9b5eae3519..98cab8d066 100644 --- a/public/templates/account.tpl +++ b/public/templates/account.tpl @@ -108,12 +108,30 @@
    - -
    - {posts.content} - +
    +
    +

    Recent Posts

    +
    +
    + +
    +

    {posts.content}

    + + + posted + in + + {posts.categoryName} + + + + +
    +
    + +
    - +
    diff --git a/public/templates/category.tpl b/public/templates/category.tpl index 849c042d9a..8dd78f12ed 100644 --- a/public/templates/category.tpl +++ b/public/templates/category.tpl @@ -30,59 +30,60 @@
  • -
    -
    -
    -
    + + + +

    + +

    + + + + [[category:posts]] + {topics.postcount} + + | + + [[category:views]] + {topics.viewcount} + + | + + [[category:posted]] + + + + + [[category:no_replies]] + + + + + + [[category:replied]] - - - [[category:posts]] - {topics.postcount} - - | - - [[category:views]] - {topics.viewcount} - - | - - - - - [[category:posted]] - + + + + - - -
    -
    +
  • -
    +
    [[category:sidebar.recent_replies]]
    diff --git a/public/templates/favourites.tpl b/public/templates/favourites.tpl index c26e4952c1..e5f8fe9cb7 100644 --- a/public/templates/favourites.tpl +++ b/public/templates/favourites.tpl @@ -13,28 +13,31 @@
    -
    - - - +
    +
    + + + - - {posts.username} - -

    {posts.content}

    + + {posts.username} + +

    {posts.content}

    -
    -
    diff --git a/public/templates/header.tpl b/public/templates/header.tpl index 78236348f8..6ee3cbb513 100644 --- a/public/templates/header.tpl +++ b/public/templates/header.tpl @@ -68,7 +68,7 @@
  • - [[global:header.search]] + [[global:header.search]]
  • @@ -99,6 +99,9 @@ +
  • + [[notifications:title]] +
  • diff --git a/public/templates/unread.tpl b/public/templates/unread.tpl index 142945b20e..114e8cba83 100644 --- a/public/templates/unread.tpl +++ b/public/templates/unread.tpl @@ -19,49 +19,56 @@
    • -
      -
      +
      + + + +

      -

      {topics.title}

      -
      - - - posts - {topics.postcount} - - | - - views - {topics.viewcount} - - | - - - - - posted in - - {topics.categoryName} - - - + + + + + + {topics.title} + +

      -
      + + + + + No one has replied + + + + + + replied + + + + +
    • diff --git a/src/posts.js b/src/posts.js index 405865bca4..2be8c7b83b 100644 --- a/src/posts.js +++ b/src/posts.js @@ -126,6 +126,60 @@ var db = require('./database'), }); }; + Posts.getPostsByPids = function(pids, callback) { + var keys = []; + + for(var x=0, numPids=pids.length; x 0 ? '' : ' default'); + data.motd_class += meta.config.motd_class ? ' ' + meta.config.motd_class : ''; + + data.motd = require('marked')(motdString); + res.json(data); + }; if (!meta.config.motd) { // Construct default MOTD - translator.mget(['global:motd.welcome', 'global:motd.get', 'global:motd.fork', 'global:motd.like', 'global:motd.follow'], function(err, strings) { - motdString = '\n\n# NodeBB \nv' + pkg.version + '\n\n' + strings[0] + - '\ + ', function(motd) { + motdString = motd; + assemble(); }); } else { motdString = meta.config.motd; + assemble(); } - data.motd_class = (parseInt(meta.config.show_motd, 10) === 1 || meta.config.show_motd === undefined) ? '' : ' none'; - data.motd_class += (meta.config.motd && meta.config.motd.length > 0 ? '' : ' default'); - data.motd_class += meta.config.motd_class ? ' ' + meta.config.motd_class : ''; - - data.motd = require('marked')(motdString); - res.json(data); }); }); }); diff --git a/src/routes/user.js b/src/routes/user.js index 40bd5713c2..48dc5c26d0 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -407,38 +407,41 @@ var fs = require('fs'), var callerUID = req.user ? req.user.uid : '0'; getUserDataByUserSlug(req.params.userslug, callerUID, function (userData) { - if (userData) { - user.isFollowing(callerUID, userData.theirid, function (isFollowing) { - posts.getPostsByUid(userData.theirid, 0, 9, function (err, posts) { + if(!userData) { + return res.json(404, { + error: 'User not found!' + }); + } - if(err) { - return next(err); - } + user.isFollowing(callerUID, userData.theirid, function (isFollowing) { - userData.posts = posts.filter(function (p) { - return p && parseInt(p.deleted, 10) !== 1; - }); + posts.getPostsByUid(callerUID, userData.theirid, 0, 9, function (err, posts) { - userData.isFollowing = isFollowing; + if(err) { + return next(err); + } - if (!userData.profileviews) { - userData.profileviews = 1; - } - if (parseInt(callerUID, 10) !== parseInt(userData.uid, 10)) { - user.incrementUserFieldBy(userData.uid, 'profileviews', 1); - } + userData.posts = posts.filter(function (p) { + return p && parseInt(p.deleted, 10) !== 1; + }); - postTools.parse(userData.signature, function (err, signature) { - userData.signature = signature; - res.json(userData); - }); + userData.isFollowing = isFollowing; + + if (!userData.profileviews) { + userData.profileviews = 1; + } + + if (parseInt(callerUID, 10) !== parseInt(userData.uid, 10)) { + user.incrementUserFieldBy(userData.uid, 'profileviews', 1); + } + + postTools.parse(userData.signature, function (err, signature) { + userData.signature = signature; + res.json(userData); }); }); - } else { - res.json(404, { - error: 'User not found!' - }); - } + }); + }); }); diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 8dd19299ed..9b1c8936cd 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -71,13 +71,23 @@ SocketTopics.postcount = function(socket, tid, callback) { topics.getTopicField(tid, 'postcount', callback); }; +SocketTopics.markAsRead = function(socket, data) { + if(!data || !data.tid || !data.uid) { + return; + } + + topics.markAsRead(data.tid, data.uid, function(err) { + topics.pushUnreadCount(data.uid); + }); +} + SocketTopics.markAllRead = function(socket, data, callback) { topics.markAllRead(socket.uid, function(err) { if(err) { return callback(err); } - index.server.sockets.in('uid_' + socket.uid).emit('event:unread.updateCount', null, 0); + index.server.sockets.in('uid_' + socket.uid).emit('event:unread.updateCount', null, []); callback(null); }); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index ea608750e4..957ec2c5d5 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -141,9 +141,7 @@ SocketUser.getOnlineAnonCount = function(socket, data, callback) { }; SocketUser.getUnreadCount = function(socket, data, callback) { - topics.getUnreadTids(socket.uid, 0, 19, function(err, tids) { - callback(err, tids?tids.length:0); - }); + topics.getUnreadTids(socket.uid, 0, 19, callback); }; SocketUser.getActiveUsers = function(socket, data, callback) { diff --git a/src/topics.js b/src/topics.js index 7b11c4f972..ba88cef81b 100644 --- a/src/topics.js +++ b/src/topics.js @@ -184,8 +184,9 @@ var async = require('async'), return callback(err, null); } - Topics.markAsRead(tid, uid); - Topics.pushUnreadCount(); + Topics.markAsRead(tid, uid, function(err) { + Topics.pushUnreadCount(null); + }); }); }); }); @@ -633,9 +634,13 @@ var async = require('async'), uids = [uids]; } + uids = uids.filter(function(value) { + return parseInt(value, 10) !== 0; + }); + async.each(uids, function(uid, next) { Topics.getUnreadTids(uid, 0, 19, function(err, tids) { - websockets.in('uid_' + uid).emit('event:unread.updateCount', null, tids.length); + websockets.in('uid_' + uid).emit('event:unread.updateCount', null, tids); next(); }); }, function(err) { @@ -764,8 +769,9 @@ var async = require('async'), // "quiet" is used for things like RSS feed updating, HTML parsing for non-js users, etc if (!quiet) { - Topics.markAsRead(tid, current_user); - Topics.pushUnreadCount(current_user); + Topics.markAsRead(tid, current_user, function(err) { + Topics.pushUnreadCount(current_user); + }); Topics.increaseViewCount(tid); } @@ -916,13 +922,15 @@ var async = require('async'), return callback(err); } - if (tids && tids.length) { - for (var i = 0; i < tids.length; ++i) { - Topics.markAsRead(tids[i], uid); - } + if(!tids || !tids.length) { + return callback(null); } - callback(null); + function markRead(tid, next) { + Topics.markAsRead(tid, uid, next); + } + + async.each(tids, markRead, callback); }); } @@ -938,9 +946,13 @@ var async = require('async'), db.delete('tid:' + tid + ':read_by_uid', callback); } - Topics.markAsRead = function(tid, uid) { + Topics.markAsRead = function(tid, uid, callback) { - db.setAdd('tid:' + tid + ':read_by_uid', uid); + db.setAdd('tid:' + tid + ':read_by_uid', uid, function(err) { + if(callback) { + callback(err); + } + }); Topics.getTopicField(tid, 'cid', function(err, cid) { diff --git a/src/user.js b/src/user.js index ed0e92a1dd..f9e6bc548a 100644 --- a/src/user.js +++ b/src/user.js @@ -471,11 +471,10 @@ var bcrypt = require('bcrypt'), } var usernames = Object.keys(usernamesHash), - filterRegex = new RegExp('^' + query + '.*?$', 'i'), results = []; results = usernames.filter(function(username) { // Remove non-matches - return filterRegex.test(username); + return username.indexOf(query) === 0; }).sort(function(a, b) { // Sort alphabetically return a > b; }).slice(0, 5) // Limit 5 @@ -933,6 +932,7 @@ var bcrypt = require('bcrypt'), notifications.get(nid, uid, function(notif_data) { // If the notification could not be found, silently drop it if (notif_data) { + notif_data.readClass = !notif_data.read ? 'label-warning' : ''; unread.push(notif_data); } else { db.sortedSetRemove('uid:' + uid + ':notifications:unread', nid); @@ -1015,7 +1015,7 @@ var bcrypt = require('bcrypt'), return parseInt(b.datetime, 10) - parseInt(a.datetime, 10); }).map(function(notif) { notif.datetimeISO = utils.toISOString(notif.datetime); - notif.readClass = !notif.read ? 'unread' : ''; + notif.readClass = !notif.read ? 'label-warning' : ''; return notif; });