diff --git a/.gitignore b/.gitignore index 6c0c33d689..d3b77831d4 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ provision.sh *.komodoproject feeds/recent.rss + +# winston? +error.log diff --git a/app.js b/app.js index 8defae9ab2..e1201f1bd1 100644 --- a/app.js +++ b/app.js @@ -25,6 +25,7 @@ var fs = require('fs'), async = require('async'), + semver = require('semver'), winston = require('winston'), pkg = require('./package.json'), path = require('path'), @@ -48,6 +49,12 @@ winston.error(err.stack); }; + require('child_process').exec('/usr/bin/which convert', function(err, stdout, stderr) { + if(err || !stdout) { + winston.warn('Couldn\'t find convert. Did you install imagemagick?'); + } + }); + // 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.'); @@ -73,6 +80,10 @@ winston.info('Base Configuration OK.'); } + if (semver.gt(pkg.dependencies['nodebb-theme-cerulean'], require('./node_modules/nodebb-theme-cerulean/package.json').version)) { + winston.error('nodebb-theme-cerulean is out of date - please run npm install.') + } + require('./src/database').init(function(err) { meta.configs.init(function () { diff --git a/package.json b/package.json index c7ba9b9586..84aa875645 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "socket.io": "~0.9.16", "redis": "0.8.3", - "mongodb": "1.3.20", + "mongodb": "~1.3.19", "express": "3.2.0", "express-namespace": "~0.1.1", "emailjs": "0.3.4", @@ -43,10 +43,11 @@ "uglify-js": "~2.4.0", "validator": "~1.5.1", "nodebb-plugin-mentions": "~0.1.15", - "nodebb-plugin-markdown": "~0.2.0", + "nodebb-plugin-markdown": "~0.2.1", "nodebb-theme-vanilla": "~0.0.9", "nodebb-theme-cerulean": "0.0.10", - "cron": "~1.0.1" + "cron": "~1.0.1", + "semver": "~2.2.1" }, "optionalDependencies": { "hiredis": "~0.1.15" diff --git a/public/language/de/topic.json b/public/language/de/topic.json index e882660101..198fb414d9 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -36,5 +36,8 @@ "loading": "Lade", "more_posts": "Mehr Posts", "move_topic": "Thema verschieben", - "topic_will_be_moved_to": "Dieses Thema wird verschoben nach" + "topic_will_be_moved_to": "Dieses Thema wird verschoben nach", + + "reputation": "Reputation", + "posts": "Posts" } diff --git a/public/language/de/user.json b/public/language/de/user.json index 492307f50d..9ca07d5433 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -7,7 +7,7 @@ "location": "Wohnort", "age": "Alter", "joined": "Beigetreten", - "profil_views": "Profilaufrufe", + "profile_views": "Profilaufrufe", "reputation": "Reputation", "posts": "Posts", "followers": "Follower", @@ -18,7 +18,7 @@ "change_picture": "Ändere Profilbild", "edit": "Ändern", - "uploaded_pictures": "Hochgeladene Bilder", + "uploaded_picture": "Hochgeladene Bilder", "upload_new_picture": "Neues Bild hochladen", "change_password": "Ändere Passwort", "confirm_password": "Passwort wiederholen", @@ -29,7 +29,7 @@ "image_spec": "Du solltest nur Dateien die PNG, JPG, oder GIF kleiner als 256kb hochladen.", "settings": "Einstellungen", - "show_my_email": "Zeige meine E-Mail Adresse an.", + "show_email": "Zeige meine E-Mail Adresse an.", "has_no_follower": "Dieser User hat noch keine Follower.", "follows_no_one": "Dieser User folgt noch niemanden." diff --git a/public/language/en/topic.json b/public/language/en/topic.json index 2584436a22..9df5829bea 100644 --- a/public/language/en/topic.json +++ b/public/language/en/topic.json @@ -32,8 +32,11 @@ "favourites.has_no_favourites": "You don't have any favourites, favourite some posts to see them here!", "posted_by": "posted by", - "loading": "Lade", + "loading": "Loading", "more_posts": "More Posts", "move_topic": "Move Topic", - "topic_will_be_moved_to": "This topic will be moved to the category" + "topic_will_be_moved_to": "This topic will be moved to the category", + + "reputation": "Reputation", + "posts": "Posts" } diff --git a/public/language/en/user.json b/public/language/en/user.json index 512fad96c3..1b58f198de 100644 --- a/public/language/en/user.json +++ b/public/language/en/user.json @@ -1,35 +1,35 @@ { "banned": "Banned", - "offline": "offline", - "email": "email", - "fullname": "full name", - "website": "website", - "location": "location", - "age": "age", - "joined": "joined", - "profil_views": "profil views", - "reputation": "reputation", - "posts": "posts", - "followers": "followers", - "following": "following", - "signature": "signature", - "gravatar": "gravatar", - "birthday": "birthday", + "offline": "Offline", + "email": "Email", + "fullname": "Full Name", + "website": "Website", + "location": "Location", + "age": "Age", + "joined": "Joined", + "profile_views": "Profile views", + "reputation": "Reputation", + "posts": "Posts", + "followers": "Followers", + "following": "Following", + "signature": "Signature", + "gravatar": "Gravatar", + "birthday": "Birthday", - "change_picture": "change picture", - "edit": "edit", - "uploaded_pictures": "uploaded pictures", - "upload_new_picture": "upload new picture", - "change_password": "change password", - "confirm_password": "confirm password", - "password": "password", + "change_picture": "Change Picture", + "edit": "Edit", + "uploaded_picture": "Uploaded Picture", + "upload_new_picture": "Upload New Picture", + "change_password": "Change Password", + "confirm_password": "Confirm Password", + "password": "Password", "upload_picture": "Upload picture", "upload_a_picture": "Upload a picture", "image_spec": "You may only upload PNG, JPG, or GIF files under 256kb.", "settings": "settings", - "show_my_email": "show my email", + "show_email": "Show My Email", "has_no_follower": "This user doesn't have any followers :(", "follows_no_one": "This user isn't following anyone :(" diff --git a/public/language/es/topic.json b/public/language/es/topic.json index 865c80a59b..97f5291258 100644 --- a/public/language/es/topic.json +++ b/public/language/es/topic.json @@ -35,5 +35,8 @@ "loading": "Cargando", "more_posts": "Más posts", "move_topic": "Mover Tema", - "topic_will_be_moved_to": "Este tema sera movido a la categoría" + "topic_will_be_moved_to": "Este tema sera movido a la categoría", + + "reputation": "Reputación", + "posts": "Posts" } \ No newline at end of file diff --git a/public/language/es/user.json b/public/language/es/user.json index f1bfdf4ed0..ab0783255c 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -1,35 +1,35 @@ { "banned": "Banneado", - "offline": "desconectado", - "email": "email", - "fullname": "nombre completo", - "website": "website", - "location": "ubicación", - "age": "edad", - "joined": "registro", - "profil_views": "visitas en su perfil", - "reputation": "reputación", - "posts": "posts", - "followers": "seguidores", - "following": "siguiendo", - "signature": "firma", - "gravatar": "gravatar", - "birthday": "cumpleaños", + "offline": "Desconectado", + "email": "Email", + "fullname": "Nombre Completo", + "website": "Website", + "location": "Ubicación", + "age": "Edad", + "joined": "Registro", + "profile_views": "Visitas en su perfil", + "reputation": "Reputación", + "posts": "Posts", + "followers": "Seguidores", + "following": "Siguiendo", + "signature": "Firma", + "gravatar": "Gravatar", + "birthday": "Cumpleaños", - "change_picture": "cambiar foto", - "edit": "editar", - "uploaded_pictures": "fotos cargadas", - "upload_new_picture": "cargar nueva foto", - "change_password": "cambiar contraseña", - "confirm_password": "confirmar contraseña", - "password": "contraseña", + "change_picture": "Cambiar Foto", + "edit": "Editar", + "uploaded_picture": "Fotos Cargadas", + "upload_new_picture": "Cargar Nueva Foto", + "change_password": "Cambiar Contraseña", + "confirm_password": "Confirmar Contraseña", + "password": "Contraseña", "upload_picture": "Cargar foto", "upload_a_picture": "Cargar una foto", "image_spec": "Solo puedes usar PNG, JPG, o GIF hasta 256kb.", - "settings": "opciones", - "show_my_email": "mostrar mi email", + "settings": "Opciones", + "show_email": "Mostrar mi Email", "has_no_follower": "Este miembro no tiene seguidores :(", "follows_no_one": "Este miembro no sigue a nadie, que pena :(" diff --git a/public/language/fr/category.json b/public/language/fr/category.json new file mode 100644 index 0000000000..29c8c492b6 --- /dev/null +++ b/public/language/fr/category.json @@ -0,0 +1,14 @@ +{ + "new_topic_button": "Nouveau Sujet", + "no_topics": "<strong>Il n'y a aucun topic dans cette catégorie.</strong><br />Pourquoi ne pas en créer un?", + "sidebar.recent_replies": "Réponses Récentes", + "sidebar.active_participants": "Participants Actifs", + "sidebar.moderators": "Modérateurs", + "posts": "messages", + "views": "vues", + "posted": "posté", + "browsing": "naviguer", + "no_replies": "Personne n'a répondu", + "replied": "répondu", + "last_edited_by": "dernière édition par" +} diff --git a/public/language/fr/footer.json b/public/language/fr/footer.json new file mode 100644 index 0000000000..27a4d0e1a9 --- /dev/null +++ b/public/language/fr/footer.json @@ -0,0 +1,10 @@ +{ + "chat.chatting_with": "Chat avec <span id=\"chat-with-name\"></span>", + "chat.placeholder": "taper le message ici, presser entrer pour envoyer", + "chat.send": "Envoyer", + "stats.online": "Online", + "stats.users": "Utilisateurs", + "stats.topics": "Sujets", + "stats.posts": "Message", + "success": "succès" +} diff --git a/public/language/fr/global.json b/public/language/fr/global.json new file mode 100644 index 0000000000..e454103779 --- /dev/null +++ b/public/language/fr/global.json @@ -0,0 +1,31 @@ +{ + "home": "Accueil", + "search": "Recherche", + "buttons.close": "Fermer", + "403.title": "Accès Refusé", + "403.message": "Il semble que vous vous soyez retrouvé sur une page dont vous n'avez pas accès. Peut-être devriez vous <a href='/login'>essayez de vous connecter</a>?", + "404.title": "Introuvable", + "404.message": "Il semble que vous vous soyez retrouvé sur une page qui n'existe pas. Retourner à <a href='/'>l'accueil</a>.", + "500.title": "Erreur Interne.", + "500.message": "Oops! Il semblerait que quelque chose se soit mal passé!", + + "register": "S'inscrire", + "login": "Connecter", + + "logout": "Déconnection", + "logout.title": "Vous êtes maintenant déconnecté.", + "logout.message": "Vous vous êtes déconnecté de NodeBB avec succès", + + "save_changes": "Enregistrer les changements", + "close": "Fermer", + + "header.admin": "Admin", + "header.recent": "Récent", + "header.unread": "Non Lu", + "header.users": "Utilisateurs", + "header.search": "Recherche", + "header.profile": "Profile", + + "notifications.loading": "Chargement des Notifications", + "chats.loading": "Chargement des Chats" +} diff --git a/public/language/fr/login.json b/public/language/fr/login.json new file mode 100644 index 0000000000..d892409f69 --- /dev/null +++ b/public/language/fr/login.json @@ -0,0 +1,10 @@ +{ + "login": "Connexion", + "username": "Identifiant", + "password": "Mot de passe", + "remember_me": "Se souvenir de moi?", + "forgot_password": "Mot de passe oublié?", + "alternative_logins": "Connexion Alternative", + "failed_login_attempt": "Echèc d'authentification, veuillez réessayer.", + "login_successful": "Vous êtes maintenant connecté!" +} diff --git a/public/language/fr/notifications.json b/public/language/fr/notifications.json new file mode 100644 index 0000000000..7a4ef3e3c3 --- /dev/null +++ b/public/language/fr/notifications.json @@ -0,0 +1,9 @@ +{ + "title": "Notifications", + "back_to_home": "retour à NodeBB", + "mark_all_as_read": "Tout marquer comme lu", + "outgoing_link": "Lien Sortant", + "outgoing_link_message": "Vous quitter NodeBB", + "continue_to": "Continuer vers", + "return_to": "Retour vers" +} diff --git a/public/language/fr/recent.json b/public/language/fr/recent.json new file mode 100644 index 0000000000..4f7d7c96cc --- /dev/null +++ b/public/language/fr/recent.json @@ -0,0 +1,5 @@ +{ + "day": "Jour", + "week": "Semaine", + "month": "Mois" +} diff --git a/public/language/fr/register.json b/public/language/fr/register.json new file mode 100644 index 0000000000..bfb58f76e9 --- /dev/null +++ b/public/language/fr/register.json @@ -0,0 +1,16 @@ +{ + "register": "S'inscrire", + "help.email": "Par défault, votre email est masqué du public.", + "help.username_restrictions": "Un identifiant unique entre %1 et %2 charactères. Les autres utilisateurs peuvent vous citer avec @<span id='yourUsername'>username</span>.", + "help.minimum_password_length": "Votre mot de passe doit avoir au moins %1 charactères.", + "email_address": "Adresse Email", + "email_address_placeholder": "Entrer l'addresse Email", + "username": "Nom d'utilisateur", + "username_placeholder": "Entré le Nom d'utilisateur", + "password": "Mot de passe", + "password_placeholder": "Entrer le Mot de passe", + "confirm_password": "Confirmer le Mot de passe", + "confirm_password_placeholder": "Confirmer le Mot de passe", + "register_now_button": "S'enregistrer maintenant", + "alternative_registration": "Enregistrement Alternatif" +} diff --git a/public/language/fr/reset_password.json b/public/language/fr/reset_password.json new file mode 100644 index 0000000000..76b5724528 --- /dev/null +++ b/public/language/fr/reset_password.json @@ -0,0 +1,13 @@ +{ + "reset_password": "Réinitialiser le Mot de passe", + "update_password": "Mettre à jour le Mot de passe", + "password_changed.title": "Mot de passe modifié", + "password_changed.message": "<p>Mot de passe réinitialisé avec succès, veuillez vous <a href=\"/login\">reconnecter</a>.", + "wrong_reset_code.title": "Code de Réinisialisation Incorrect", + "wrong_reset_code.message": "Le Code de Réinisialisation est Incorrect. Veillez réessayer, ou <a href=\"/reset\">demander un nouveau Code de Réinisialisation</a>.", + "new_password": "Nouveau Mot de passe", + "repeat_password": "Confirmer le Mot de passe", + "enter_email": "Veuillez entrer votre <strong>adresse email</strong> et vous recevrez un email avec les instruction pour réinitialiser votre compte.", + "password_reset_sent": "Réinitialisation de Mot de Passe Envoyée", + "invalid_email": "Email Invalide / L'Email n'existe pas!" +} diff --git a/public/language/fr/topic.json b/public/language/fr/topic.json new file mode 100644 index 0000000000..d874927c33 --- /dev/null +++ b/public/language/fr/topic.json @@ -0,0 +1,42 @@ +{ + "topic": "Sujet", + "topics": "Sujets", + + "no_topics_found": "Aucun sujet trouvé!", + + "profile": "Profile", + "posted_by": "Envoyé by", + "chat": "Chat", + "notify_me": "Être notifié des réponses dans ce sujet", + "quote": "Citer", + "reply": "Répondre", + "edit": "Editer", + "delete": "Supprimer", + "banned": "bannir", + "link": "Lien", + + "thread_tools.title": "Outils du Fil", + "thread_tools.pin": "Epingler le fil", + "thread_tools.lock": "Verrouiller le fil", + "thread_tools.move": "Déplacer le fil", + "thread_tools.delete": "Supprimer le fil", + + "load_categories": "Chargement des Categories", + "disabled_categories_note": "Les Catégories Désactivées sont grisées", + "confirm_move": "Déplacer", + + "favourite": "Favoris", + "favourites": "Favoris", + "favourites.not_logged_in.title": "Non Connecté", + "favourites.not_logged_in.message": "Veuillez vous connecter avant de mettre ce message en Favoris", + "favourites.has_no_favourites": "Vous n'avez aucun Favoris, mettre en favoris des messages pour les voir apparaître ici!", + + "posted_by": "posté par", + "loading": "Chargement", + "more_posts": "d'autres Messages", + "move_topic": "Déplacer le Sujet", + "topic_will_be_moved_to": "Ce sujet sera déplacé vers la catégorie", + + "reputation": "réputation", + "posts": "messages" +} diff --git a/public/language/fr/unread.json b/public/language/fr/unread.json new file mode 100644 index 0000000000..78f51ce820 --- /dev/null +++ b/public/language/fr/unread.json @@ -0,0 +1,5 @@ +{ + "no_unread_topics": "Aucun sujet non lu.", + "mark_all_read": "Marquer tout comme lu", + "load_more": "Charger la suite" +} diff --git a/public/language/fr/user.json b/public/language/fr/user.json new file mode 100644 index 0000000000..faa9883d47 --- /dev/null +++ b/public/language/fr/user.json @@ -0,0 +1,36 @@ +{ + "banned": "Banni", + "offline": "Hors-ligne", + "email": "email", + "fullname": "Nom", + "website": "Site Web", + "location": "Emplacement", + "age": "age", + "joined": "adhésion", + "profil_views": "vues du profil", + "reputation": "réputation", + "posts": "messages", + "followers": "suiveurs", + "following": "suivis", + "signature": "signature", + "gravatar": "gravatar", + "birthday": "anniversaire", + + "change_picture": "changer d'image", + "edit": "editer", + "uploaded_picture": "images uploadées", + "upload_new_picture": "uploader une nouvelle image", + "change_password": "chnger le mot de passe", + "confirm_password": "confirmer le mot de passe", + "password": "mot de passe", + + "upload_picture": "Uploader un image", + "upload_a_picture": "Uploader un image", + "image_spec": "Vous pouvez uploader seulement des fichiers de types PNG, JPG, ou GIF en dessous de 256kb.", + + "settings": "paramètres", + "show_my_email": "montrer mon email", + + "has_no_follower": "Cet utilisateur n'a aucun suiver :(", + "follows_no_one": "Cet utilisateur ne suit personne :(" +} diff --git a/public/language/fr/users.json b/public/language/fr/users.json new file mode 100644 index 0000000000..8cab0e5298 --- /dev/null +++ b/public/language/fr/users.json @@ -0,0 +1,9 @@ +{ + "latest_users": "Derniers Utilisateurs", + "top_posters": "Meilleurs Publieur", + "most_reputation": "Meilleur Réputation", + "online": "En Ligne", + "search": "Rechercher", + "enter_username": "Entrer un nom d'utilisateur pour rechercher", + "load_more": "Charger la suite" +} diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index bba039ea66..e0c69687f6 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -31,6 +31,8 @@ var ajaxify = {}; var pagination, paginator_bar; + ajaxify.currentPage = null; + 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'); @@ -69,6 +71,8 @@ var ajaxify = {}; } if (templates.is_available(tpl_url) && !templates.force_refresh(tpl_url)) { + ajaxify.currentPage = tpl_url; + if (window.history && window.history.pushState) { window.history[!quiet ? 'pushState' : 'replaceState']({ url: url @@ -90,7 +94,7 @@ var ajaxify = {}; translator.load(tpl_url); - jQuery('#footer, #content').addClass('ajaxifying'); + jQuery('#footer, #content').removeClass('hide').addClass('ajaxifying'); templates.flush(); templates.load_template(function () { @@ -129,6 +133,10 @@ var ajaxify = {}; return false; }; + ajaxify.refresh = function() { + ajaxify.go(ajaxify.currentPage); + }; + $('document').ready(function () { if (!window.history || !window.history.pushState) { return; // no ajaxification for old browsers @@ -154,7 +162,7 @@ var ajaxify = {}; return; } - if (!e.ctrlKey && e.which === 1) { + if ((!e.ctrlKey && !e.shiftKey) && e.which === 1) { if (this.host === window.location.host) { // Internal link var url = this.href.replace(rootUrl + '/', ''); diff --git a/public/src/app.js b/public/src/app.js index c7fe7835e7..537d0103bd 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -1,16 +1,16 @@ var socket, config, app = { - 'username': null, - 'uid': null + "username": null, + "uid": null, + "isFocused": true, + "currentRoom": null }; (function () { var showWelcomeMessage = false; - app.loadConfig = function() { - $.ajax({ url: RELATIVE_PATH + '/api/config', success: function (data) { @@ -135,7 +135,7 @@ var socket, }, async: false }); - } + }; app.logout = function() { $.post(RELATIVE_PATH + '/logout', { @@ -143,12 +143,12 @@ var socket, }, function() { window.location.href = RELATIVE_PATH + '/'; }); - } + }; // takes a string like 1000 and returns 1,000 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) { @@ -159,7 +159,7 @@ var socket, return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) { return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; }); - } + }; // use unique alert_id to have multiple alerts visible at a time, use the same alert_id to fade out the current instance // type : error, success, info, warning/notify @@ -222,7 +222,7 @@ var socket, }); } } - } + }; app.alertSuccess = function (message, timeout) { if (!timeout) @@ -234,7 +234,7 @@ var socket, type: 'success', timeout: timeout }); - } + }; app.alertError = function (message, timeout) { if (!timeout) @@ -246,9 +246,8 @@ var socket, type: 'danger', timeout: timeout }); - } + }; - app.currentRoom = null; app.enterRoom = function (room) { if (socket) { if (app.currentRoom === room) { @@ -272,7 +271,7 @@ var socket, }); socket.emit('api:user.get_online_users', uids); - } + }; function highlightNavigationLink() { var path = window.location.pathname, @@ -291,7 +290,7 @@ var socket, } }); } - } + }; app.createUserTooltips = function() { $('img[title].teaser-pic,img[title].user-img').each(function() { @@ -300,13 +299,13 @@ var socket, title: $(this).attr('title') }); }); - } + }; app.makeNumbersHumanReadable = function(elements) { elements.each(function() { $(this).html(utils.makeNumberHumanReadable($(this).attr('title'))); }); - } + }; app.processPage = function () { app.populateOnlineUsers(); @@ -323,7 +322,7 @@ var socket, setTimeout(function () { window.scrollTo(0, 1); // rehide address bar on mobile after page load completes. }, 100); - } + }; app.showLoginMessage = function () { function showAlert() { @@ -343,13 +342,13 @@ var socket, showAlert(); } } - } + }; app.addCommasToNumbers = function () { $('.formatted-number').each(function (index, element) { $(element).html(app.addCommas($(element).html())); }); - } + }; app.openChat = function (username, touid) { if (username === app.username) { @@ -384,7 +383,7 @@ var socket, chat.load(chatModal.attr('UUID')); chat.center(chatModal); }); - } + }; app.scrollToTop = function () { $('body,html').animate({ @@ -442,6 +441,14 @@ var socket, input.val(''); return false; }); + + $(window).blur(function(){ + app.isFocused = false; + }); + + $(window).focus(function(){ + app.isFocused = true; + }); }); showWelcomeMessage = location.href.indexOf('loggedin') !== -1; diff --git a/public/src/forum/accountedit.js b/public/src/forum/accountedit.js index 5e4eb0fcc3..f4c86f3ba4 100644 --- a/public/src/forum/accountedit.js +++ b/public/src/forum/accountedit.js @@ -84,7 +84,9 @@ define(['forum/accountheader', 'uploader'], function(header, uploader) { $('#uploadPictureBtn').on('click', function() { $('#change-picture-modal').modal('hide'); - uploader.open(config.relative_path + '/user/uploadpicture', function(imageUrlOnServer) { + uploader.open(RELATIVE_PATH + '/user/uploadpicture', function(imageUrlOnServer) { + imageUrlOnServer = imageUrlOnServer + '?' + new Date().getTime(); + $('#user-current-picture').attr('src', imageUrlOnServer); $('#user-uploaded-picture').attr('src', imageUrlOnServer); diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index d94eb9434b..0874ffa878 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -1,4 +1,4 @@ -define(function() { +define(['uploader'], function(uploader) { var Categories = {}; Categories.init = function() { @@ -82,7 +82,8 @@ define(function() { description: $('#inputDescription').val(), icon: $('#new-category-modal i').val(), bgColor: '#0059b2', - color: '#fff' + color: '#fff', + order: $('.admin-categories #entry-container').children().length + 1 }; socket.emit('api:admin.categories.create', category, function(err, data) { @@ -147,7 +148,6 @@ define(function() { var btn = $(this); var categoryRow = btn.parents('li'); var cid = categoryRow.attr('data-cid'); - console.log(this.getAttribute('data-disabled')); var disabled = this.getAttribute('data-disabled') === '0' ? '1' : '0'; categoryRow.remove(); @@ -179,6 +179,31 @@ define(function() { var cid = $(this).parents('li[data-cid]').attr('data-cid'); Categories.launchPermissionsModal(cid); }); + + + $('.upload-button').on('click', function() { + var inputEl = this; + + uploader.open(RELATIVE_PATH + '/admin/category/uploadpicture', function(imageUrlOnServer) { + inputEl.value = imageUrlOnServer; + $(inputEl).parents('li[data-cid]').find('.preview-box').css('background', 'url(' + imageUrlOnServer + '?' + new Date().getTime() + ')'); + modified(inputEl); + }); + }); + + $('.admin-categories').delegate('.delete-image', 'click', function() { + var parent = $(this).parents('li[data-cid]'), + inputEl = parent.find('.upload-button'), + preview = parent.find('.preview-box'), + bgColor = parent.find('.category_bgColor').val(); + + inputEl.value = ''; + modified(inputEl); + + preview.css('background', bgColor); + + $(this).hide(); + }); }); }; diff --git a/public/src/forum/admin/settings.js b/public/src/forum/admin/settings.js index 989e94a79c..8e8fc359df 100644 --- a/public/src/forum/admin/settings.js +++ b/public/src/forum/admin/settings.js @@ -76,7 +76,7 @@ define(['uploader'], function(uploader) { $('#uploadLogoBtn').on('click', function() { - uploader.open(config.relative_path + '/admin/uploadlogo', function(image) { + uploader.open(RELATIVE_PATH + '/admin/uploadlogo', function(image) { $('#logoUrl').val(image); }); diff --git a/public/src/forum/category.js b/public/src/forum/category.js index 44a11eaea9..6b25a85701 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -83,36 +83,39 @@ define(function () { Category.onNewTopic = function(data) { var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: [data] - }), - topic = $(html), - container = $('#topics-container'), - topics = $('#topics-container').children('.category-item'), - numTopics = topics.length; - - jQuery('#topics-container, .category-sidebar').removeClass('hidden'); - jQuery('#category-no-topics').remove(); - - if (numTopics > 0) { - for (var x = 0; x < numTopics; x++) { - if ($(topics[x]).find('.fa-thumb-tack').length) { - if(x === numTopics - 1) { - topic.insertAfter(topics[x]); + }); + + translator.translate(html, function(translatedHTML) { + var topic = $(translatedHTML), + container = $('#topics-container'), + topics = $('#topics-container').children('.category-item'), + numTopics = topics.length; + + jQuery('#topics-container, .category-sidebar').removeClass('hidden'); + jQuery('#category-no-topics').remove(); + + if (numTopics > 0) { + for (var x = 0; x < numTopics; x++) { + if ($(topics[x]).find('.fa-thumb-tack').length) { + if(x === numTopics - 1) { + topic.insertAfter(topics[x]); + } + continue; } - continue; + topic.insertBefore(topics[x]); + break; } - topic.insertBefore(topics[x]); - break; + } else { + container.append(topic); } - } else { - container.append(topic); - } - topic.hide().fadeIn('slow'); - socket.emit('api:categories.getRecentReplies', templates.get('category_id')); + topic.hide().fadeIn('slow'); + socket.emit('api:categories.getRecentReplies', templates.get('category_id')); - addActiveUser(data); + addActiveUser(data); - $('#topics-container span.timeago').timeago(); + $('#topics-container span.timeago').timeago(); + }); } function addActiveUser(data) { @@ -131,20 +134,22 @@ define(function () { } Category.onTopicsLoaded = function(topics) { - var html = templates.prepare(templates['category'].blocks['topics']).parse({ topics: topics - }), - container = $('#topics-container'); + }); + + translator.translate(html, function(translatedHTML) { + var container = $('#topics-container'); - jQuery('#topics-container, .category-sidebar').removeClass('hidden'); - jQuery('#category-no-topics').remove(); + jQuery('#topics-container, .category-sidebar').removeClass('hidden'); + jQuery('#category-no-topics').remove(); - html = $(html); - container.append(html); + html = $(translatedHTML); + container.append(html); - $('#topics-container span.timeago').timeago(); - app.makeNumbersHumanReadable(html.find('.human-readable-number')); + $('#topics-container span.timeago').timeago(); + app.makeNumbersHumanReadable(html.find('.human-readable-number')); + }); } Category.loadMoreTopics = function(cid) { diff --git a/public/src/forum/footer.js b/public/src/forum/footer.js index eb8d0f3e20..dc89b15f01 100644 --- a/public/src/forum/footer.js +++ b/public/src/forum/footer.js @@ -67,6 +67,7 @@ notifTrigger = notifContainer.querySelector('a'), notifList = document.getElementById('notif-list'), notifIcon = $('.notifications a'); + notifTrigger.addEventListener('click', function(e) { e.preventDefault(); if (notifContainer.className.indexOf('open') === -1) { @@ -169,6 +170,10 @@ }); app.refreshTitle(); + if (ajaxify.currentPage === 'notifications') { + ajaxify.refresh(); + } + // Update the favicon + local storage var savedCount = parseInt(localStorage.getItem('notifications:count'),10) || 0; localStorage.setItem('notifications:count', savedCount+1); @@ -208,7 +213,7 @@ }); }); - socket.on('chatMessage', function(data) { + socket.on('event:chats.receive', function(data) { require(['chat'], function(chat) { var modal = null; if (chat.modalExists(data.fromuid)) { @@ -219,6 +224,9 @@ chat.load(modal.attr('UUID')); } else { chat.toggleNew(modal.attr('UUID'), true); + } + + if (!modal.is(":visible") || !app.isFocused) { app.alternatingTitle(data.username + ' has messaged you'); } } else { diff --git a/public/src/forum/recent.js b/public/src/forum/recent.js index 6b162db34f..c883c9c54b 100644 --- a/public/src/forum/recent.js +++ b/public/src/forum/recent.js @@ -81,18 +81,20 @@ define(function() { } Recent.onTopicsLoaded = function(topics) { - var html = templates.prepare(templates['recent'].blocks['topics']).parse({ topics: topics - }), - container = $('#topics-container'); + }); + + translator.translate(html, function(translatedHTML) { + var container = $('#topics-container'); - $('#category-no-topics').remove(); + $('#category-no-topics').remove(); - html = $(html); - container.append(html); - $('span.timeago').timeago(); - app.makeNumbersHumanReadable(html.find('.human-readable-number')); + html = $(html); + container.append(html); + $('span.timeago').timeago(); + app.makeNumbersHumanReadable(html.find('.human-readable-number')); + }); } Recent.loadMoreTopics = function() { diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index fd77d2084f..3a47ce901c 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -802,8 +802,8 @@ define(function() { pagination.parentNode.style.display = 'block'; progressBarContainer.css('display', ''); - - if (scrollTop < 50 && Topic.postCount > 1) { + + if (scrollTop < jQuery('.posts > .post-row:first-child').height() && Topic.postCount > 1) { localStorage.removeItem("topic:" + tid + ":bookmark"); pagination.innerHTML = '1 out of ' + Topic.postCount; progressBar.width(0); diff --git a/public/src/forum/unread.js b/public/src/forum/unread.js index 6debeeba84..e0dd1dd7d7 100644 --- a/public/src/forum/unread.js +++ b/public/src/forum/unread.js @@ -71,18 +71,20 @@ define(function() { }); function onTopicsLoaded(topics) { - var html = templates.prepare(templates['unread'].blocks['topics']).parse({ topics: topics - }), - container = $('#topics-container'); + }); - $('#category-no-topics').remove(); + translator.translate(html, function(translatedHTML) { + var container = $('#topics-container'); - html = $(html); - container.append(html); - $('span.timeago').timeago(); - app.makeNumbersHumanReadable(html.find('.human-readable-number')); + $('#category-no-topics').remove(); + + html = $(translatedHTML); + container.append(html); + $('span.timeago').timeago(); + app.makeNumbersHumanReadable(html.find('.human-readable-number')); + }); } function loadMoreTopics() { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 2aeb2f02bf..490f6e571b 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -103,6 +103,7 @@ define(['taskbar'], function(taskbar) { module.bringModalToTop(chatModal); checkOnlineStatus(chatModal); taskbar.updateActive(uuid); + chatModal.find('#chat-message-input').focus(); } module.minimize = function(uuid) { @@ -114,7 +115,7 @@ define(['taskbar'], function(taskbar) { } function getChatMessages(chatModal, callback) { - socket.emit('getChatMessages', {touid:chatModal.touid}, function(messages) { + socket.emit('api:chats.get', {touid:chatModal.touid}, function(messages) { for(var i = 0; i<messages.length; ++i) { module.appendChatMessage(chatModal, messages[i].content, messages[i].timestamp); } @@ -141,7 +142,7 @@ define(['taskbar'], function(taskbar) { var msg = app.strip_tags(chatModal.find('#chat-message-input').val()); if(msg.length) { msg = msg +'\n'; - socket.emit('sendChatMessage', { touid:chatModal.touid, message:msg}); + socket.emit('api:chats.send', { touid:chatModal.touid, message:msg}); chatModal.find('#chat-message-input').val(''); } } diff --git a/public/src/templates.js b/public/src/templates.js index 6772f998b7..e629ef08b6 100644 --- a/public/src/templates.js +++ b/public/src/templates.js @@ -300,14 +300,7 @@ namespace = namespace.replace(d + '.', ''); 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; - - block = parse(data[d], namespace, block); - template = setBlock(regex, block, template); + template = parse(data[d], d + '.', template); } else { function checkConditional(key, value) { var conditional = makeConditionalRegex(key), @@ -320,14 +313,16 @@ if (conditionalBlock[1]) { // there is an else statement if (!value) { - template = template.replace(matches[i], conditionalBlock[1]); + template = template.replace(matches[i], conditionalBlock[1].replace(/<!-- ((\IF\b)|(\bENDIF\b))([^@]*?)-->/gi, '')); } else { - template = template.replace(matches[i], conditionalBlock[0]); + template = template.replace(matches[i], conditionalBlock[0].replace(/<!-- ((\IF\b)|(\bENDIF\b))([^@]*?)-->/gi, '')); } } else { // regular if statement if (!value) { template = template.replace(matches[i], ''); + } else { + template = template.replace(matches[i], matches[i].replace(/<!-- ((\IF\b)|(\bENDIF\b))([^@]*?)-->/gi, '')); } } } @@ -350,7 +345,11 @@ if (namespace) { var regex = new RegExp("{" + namespace + "[\\s\\S]*?}", 'g'); template = template.replace(regex, ''); + namespace = ''; } + + // clean up all undefined conditionals + template = template.replace(/<!-- IF([^@]*?)ENDIF([^@]*?)-->/gi, ''); return template; diff --git a/public/templates/account.tpl b/public/templates/account.tpl index da0014b0fe..200fd854c2 100644 --- a/public/templates/account.tpl +++ b/public/templates/account.tpl @@ -53,7 +53,7 @@ <span class="timeago" title="{joindate}"></span> <br/> - <span class="account-bio-label">[[user:profil_views]]</span> + <span class="account-bio-label">[[user:profile_views]]</span> <span class="formatted-number">{profileviews}</span> <br/> diff --git a/public/templates/accountedit.tpl b/public/templates/accountedit.tpl index e9b970a1be..fa0e64d055 100644 --- a/public/templates/accountedit.tpl +++ b/public/templates/accountedit.tpl @@ -10,17 +10,17 @@ <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">[[user: gravatar]]</span> + <span class="user-picture-label">[[user:gravatar]]</span> <i class='fa fa-check fa-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">[[user: uploaded_picture]]</span> + <span class="user-picture-label">[[user:uploaded_picture]]</span> <i class='fa fa-check fa-2x'></i> </div> - <a id="uploadPictureBtn" href="#">[[user: upload_new_picture]]</a> + <a id="uploadPictureBtn" href="#">[[user:upload_new_picture]]</a> </div> <div class="modal-footer"> <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button> diff --git a/public/templates/accountsettings.tpl b/public/templates/accountsettings.tpl index f705621153..f3987b69cc 100644 --- a/public/templates/accountsettings.tpl +++ b/public/templates/accountsettings.tpl @@ -12,7 +12,7 @@ <h4>privacy</h4> <div class="checkbox"> <label> - <input id="showemailCheckBox" type="checkbox" {showemail}> [[user:show_my_email]] + <input id="showemailCheckBox" type="checkbox" {showemail}> [[user:show_email]] </label> </div> </div> diff --git a/public/templates/admin/categories.tpl b/public/templates/admin/categories.tpl index 4cc65d8bca..a8da120e82 100644 --- a/public/templates/admin/categories.tpl +++ b/public/templates/admin/categories.tpl @@ -14,12 +14,15 @@ <!-- BEGIN categories --> <li data-cid="{categories.cid}" class="entry-row"> <div class="row"> - <div class="col-sm-2 hidden-xs"> - <div class="preview-box" style="background: {categories.bgColor}; color: {categories.color};"> + <div class="col-sm-2 hidden-xs text-center"> + <div class="preview-box" style="background: {categories.background}; color: {categories.color};"> <div class="icon"> <i data-name="icon" value="{categories.icon}" class="fa {categories.icon} fa-2x"></i> </div> - </div> + </div><br /> + <!-- IF categories.image --> + <small class="pointer delete-image"><i data-name="icon" value="fa-times" class="fa fa-times"></i> Delete Image</small> + <!-- ENDIF categories.image --> </div> <div class="col-sm-10"> <form class="form"> @@ -67,11 +70,8 @@ <hr /> <li data-disabled="{categories.disabled}"><a href="#"></a></li> </ul> + <button type="button" data-name="image" data-value="{categories.image}" class="btn btn-default upload-button"><i class="fa fa-upload"></i> Image</button> </div> - <!-- <div class="btn-group"> - <button type="submit" class="btn btn-default disable-btn" data-disabled="{categories.disabled}">Disable</button> - <button type="button" class="btn btn-default permissions">Permissions</button> - </div> --> </div> </div> </div> @@ -80,17 +80,7 @@ <input type="hidden" data-name="order" data-value="{categories.order}"></input> </form> </div> - <!-- <form class="form-inline"> - <div class="icon"> - <i data-name="icon" value="{categories.icon}" class="fa {categories.icon} fa-2x"></i> - </div> - <input placeholder="Category Name" data-name="name" value="{categories.name}" class="form-control category_name"></input> - <input placeholder="#0059b2" data-name="bgColor" value="{categories.bgColor}" class="form-control category_bgColor" /> - <input placeholder="#fff" data-name="color" value="{categories.color}" class="form-control category_color" /> - <input data-name="description" placeholder="Category Description" value="{categories.description}" class="form-control category_description description"></input> - <input type="hidden" data-name="order" data-value="{categories.order}"></input> - <button type="submit" class="btn btn-default disable-btn" data-disabled="{categories.disabled}">Disable</button> - </form> --> + </div> </li> <!-- END categories --> diff --git a/public/templates/admin/settings.tpl b/public/templates/admin/settings.tpl index da88d2003e..a5e6192b1b 100644 --- a/public/templates/admin/settings.tpl +++ b/public/templates/admin/settings.tpl @@ -26,6 +26,11 @@ <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="form-control" value="1000" data-field="privileges:manage_topic"><br /> <strong>Manage Content</strong><br /> <input type="text" class="form-control" value="1000" data-field="privileges:manage_content"><br /> + <div class="checkbox"> + <label> + <input type="checkbox" data-field="privileges:disabled"> <strong>Disable Privilege Threshold System</strong> + </label> + </div> </div> </form> @@ -55,6 +60,17 @@ </div> </form> +<form> + <h3>Profile Settings</h3> + <div class="alert alert-warning"> + <div class="checkbox"> + <label> + <input type="checkbox" data-field="profile:convertProfileImageToPNG"> <strong>Convert profile image uploads to PNG</strong> + </label> + </div> + </div> +</form> + <form> <h3>User Settings</h3> <div class="alert alert-warning"> diff --git a/public/templates/footer.tpl b/public/templates/footer.tpl index a9f51f2242..4434336ed8 100644 --- a/public/templates/footer.tpl +++ b/public/templates/footer.tpl @@ -59,7 +59,7 @@ <div id="alert_window"></div> - <footer id="footer" class="container footer"> + <footer id="footer" class="container footer hide"> {footerHTML} <div class="copyright"> Copyright © 2013 <a target="_blank" href="http://www.nodebb.com">NodeBB Forums</a> | <a target="_blank" href="//github.com/designcreateplay/NodeBB/graphs/contributors">Contributors</a> diff --git a/public/templates/home.tpl b/public/templates/home.tpl index 2c8a92e801..13d7bd6caf 100644 --- a/public/templates/home.tpl +++ b/public/templates/home.tpl @@ -8,7 +8,7 @@ <a href="category/{categories.slug}" itemprop="url"> <meta itemprop="name" content="{categories.name}"> <h4><span class="badge {categories.badgeclass}">{categories.topic_count} </span> {categories.name}</h4> - <div class="icon" style="background: {categories.bgColor}; color: {categories.color};"> + <div class="icon" style="background: {categories.background}; color: {categories.color};"> <div id="category-{categories.cid}" class="category-slider-{categories.post_count}"> <div class="category-box"><i class="fa {categories.icon} fa-4x"></i></div> <div class="category-box" itemprop="description">{categories.description}</div> diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl index 47d068dcef..7802c41524 100644 --- a/public/templates/topic.tpl +++ b/public/templates/topic.tpl @@ -9,198 +9,199 @@ <input type="hidden" template-variable="facebook-share-url" value="{facebook-share-url}" /> <input type="hidden" template-variable="google-share-url" value="{google-share-url}" /> -<div class="container"> - <div class="topic row"> - <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 itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb"> - <a href="/category/{category_slug}" itemprop="url"><span itemprop="title">{category_name}</span></a> - </li> - <li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb"> - <span itemprop="title">{topic_name} <a target="_blank" href="../{topic_id}.rss"><i class="fa fa-rss-square"></i></a></span> - </li> - - </ol> - <ul id="post-container" class="container posts" data-tid="{topic_id}"> - <!-- BEGIN posts --> - <li class="row post-row infiniteloaded" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-index="{posts.index}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment"> - <a id="post_anchor_{posts.pid}" name="{posts.pid}"></a> +<div class="topic"> + <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 itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="/category/{category_slug}" itemprop="url"><span itemprop="title">{category_name}</span></a> + </li> + <li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb"> + <span itemprop="title">{topic_name} <a target="_blank" href="../{topic_id}.rss"><i class="fa fa-rss-square"></i></a></span> + </li> + + </ol> + + <ul id="post-container" class="posts" data-tid="{topic_id}"> + <!-- BEGIN posts --> + <li class="row post-row infiniteloaded" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-index="{posts.index}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment"> + <a id="post_anchor_{posts.pid}" name="{posts.pid}"></a> + + <meta itemprop="datePublished" content="{posts.relativeTime}"> + <meta itemprop="dateModified" content="{posts.relativeEditTime}"> + + <div class="col-md-1 profile-image-block hidden-xs hidden-sm sub-post"> + <a href="/user/{posts.userslug}"> + <img src="{posts.picture}" align="left" class="img-thumbnail" itemprop="image" /> + <!-- IF posts.user_banned --> + <span class="label label-danger">[[topic:banned]]</span> + <!-- ENDIF posts.user_banned --> + </a> - <meta itemprop="datePublished" content="{posts.relativeTime}"> - <meta itemprop="dateModified" content="{posts.relativeEditTime}"> + </div> - <div class="col-md-1 profile-image-block hidden-xs hidden-sm sub-post"> - <a href="/user/{posts.userslug}"> - <img src="{posts.picture}" align="left" class="img-thumbnail" itemprop="image" /> - <!-- IF posts.user_banned --> - <span class="label label-danger">[[topic:banned]]</span> - <!-- ENDIF posts.user_banned --> + <div class="col-md-11"> + <div class="post-block"> + <a class="main-post avatar" href="/user/{posts.userslug}"> + <img itemprop="image" src="{posts.picture}" align="left" class="img-thumbnail" width=150 height=150 /> </a> - </div> + <h3 class="main-post"> + <p id="topic_title_{posts.pid}" class="topic-title" itemprop="name">{topic_name}</p> + </h3> + + <div class="topic-buttons"> + <div class="btn-group"> + <button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" type="button" title="[[topic:posted_by]] {posts.username}"> + <span class="username-field" href="/user/{posts.userslug}" itemprop="author">{posts.username} </span> + <span class="caret"></span> + </button> + + <ul class="dropdown-menu"> + <li><a href="/user/{posts.userslug}"><i class="fa fa-user"></i> [[topic:profile]]</a></li> + <li><div class="chat"><i class="fa fa-comment"></i> [[topic:chat]]</div></li> + </ul> + </div> - <div class="col-md-11"> - <div class="post-block"> - <a class="main-post avatar" href="/user/{posts.userslug}"> - <img itemprop="image" src="{posts.picture}" align="left" class="img-thumbnail" width=150 height=150 /> - </a> - <h3 class="main-post"> - <p id="topic_title_{posts.pid}" class="topic-title" itemprop="name">{topic_name}</p> - </h3> - - <div class="topic-buttons"> - <div class="btn-group"> - <button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" type="button" title="[[topic:posted_by]] {posts.username}"> - <span class="username-field" href="/user/{posts.userslug}" itemprop="author">{posts.username} </span> - <span class="caret"></span> - </button> - - <ul class="dropdown-menu"> - <li><a href="/user/{posts.userslug}"><i class="fa fa-user"></i> [[topic:profile]]</a></li> - <li><div class="chat"><i class="fa fa-comment"></i> [[topic:chat]]</div></li> - </ul> - </div> + <div class="btn-group"> + <!-- IF @first --> + <button class="btn btn-sm btn-default follow" type="button" title="Be notified of new replies in this topic"><i class="fa fa-eye"></i></button> + <!-- ENDIF @first --> + <button data-favourited="{posts.favourited}" class="favourite btn btn-sm btn-default <!-- IF posts.favourited --> btn-warning <!-- ENDIF posts.favourited -->" type="button"> + <span class="favourite-text">[[topic:favourite]]</span> + <span class="post_rep_{posts.pid}">{posts.reputation} </span> + <!-- IF posts.favourited --> + <i class="fa fa-star"></i> + <!-- ELSE --> + <i class="fa fa-star-o"></i> + <!-- ENDIF posts.favourited --> + </button> + </div> + <div class="btn-group"> + <button class="btn btn-sm btn-default quote" type="button" title="[[topic:quote]]"><i class="fa fa-quote-left"></i></button> + <button class="btn btn-sm btn-primary btn post_reply" type="button">[[topic:reply]] <i class="fa fa-reply"></i></button> + </div> - <div class="btn-group"> - <!-- IF @first --> - <button class="btn btn-sm btn-default follow" type="button" title="Be notified of new replies in this topic"><i class="fa fa-eye"></i></button> - <!-- ENDIF @first --> - <button data-favourited="{posts.favourited}" class="favourite btn btn-sm btn-default <!-- IF posts.favourited --> btn-warning <!-- ENDIF posts.favourited -->" type="button"> - <span class="favourite-text">[[topic:favourite]]</span> - <span class="post_rep_{posts.pid}">{posts.reputation} </span> - <!-- IF posts.favourited --> - <i class="fa fa-star"></i> - <!-- ELSE --> - <i class="fa fa-star-o"></i> - <!-- ENDIF posts.favourited --> - </button> - </div> - <div class="btn-group"> - <button class="btn btn-sm btn-default quote" type="button" title="[[topic:quote]]"><i class="fa fa-quote-left"></i></button> - <button class="btn btn-sm btn-primary btn post_reply" type="button">[[topic:reply]] <i class="fa fa-reply"></i></button> + <div class="pull-right"> + <div class="btn-group post-tools"> + <button class="btn btn-sm btn-default link" type="button" title="[[topic:link]]"><i class="fa fa-link"></i></button> + <button class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="fa fa-facebook"></i></button> + <button class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="fa fa-twitter"></i></button> + <button class="btn btn-sm btn-default google-share" type="button" title=""><i class="fa fa-google-plus"></i></button> </div> - <div class="pull-right"> - <div class="btn-group post-tools"> - <button class="btn btn-sm btn-default link" type="button" title="[[topic:link]]"><i class="fa fa-link"></i></button> - <button class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="fa fa-facebook"></i></button> - <button class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="fa fa-twitter"></i></button> - <button class="btn btn-sm btn-default google-share" type="button" title=""><i class="fa fa-google-plus"></i></button> - </div> - - <!-- IF posts.display_moderator_tools --> - <div class="btn-group post-tools"> - <button class="btn btn-sm btn-default edit" type="button" title="[[topic:edit]]"><i class="fa fa-pencil"></i></button> - <button class="btn btn-sm btn-default delete" type="button" title="[[topic:delete]]"><i class="fa fa-trash-o"></i></button> - </div> - <!-- ENDIF posts.display_moderator_tools --> + <!-- IF posts.display_moderator_tools --> + <div class="btn-group post-tools"> + <button class="btn btn-sm btn-default edit" type="button" title="[[topic:edit]]"><i class="fa fa-pencil"></i></button> + <button class="btn btn-sm btn-default delete" type="button" title="[[topic:delete]]"><i class="fa fa-trash-o"></i></button> </div> - - <input id="post_{posts.pid}_link" value="" class="pull-right" style="display:none;"></input> + <!-- ENDIF posts.display_moderator_tools --> </div> - <div id="content_{posts.pid}" class="post-content" itemprop="text">{posts.content}</div> - <!-- IF posts.signature --> - <div class="post-signature">{posts.signature}</div> - <!-- ENDIF posts.signature --> - - <div class="post-info"> - <span class="pull-left"> - {posts.additional_profile_info} - </span> - <span class="pull-right"> - [[category:posted]] <span class="relativeTimeAgo timeago" title="{posts.relativeTime}"></span> - <!-- IF posts.editor --> - <span>| [[category:last_edited_by]] <strong><a href="/user/{posts.editorslug}">{posts.editorname}</a></strong></span> - <span class="timeago" title="{posts.relativeEditTime}"></span> - <!-- ENDIF posts.editor --> - </span> - <div style="clear:both;"></div> - </div> + <input id="post_{posts.pid}_link" value="" class="pull-right" style="display:none;"></input> </div> - </div> - </li> - - <!-- IF @first --> - <li class="well post-bar"> - <div class="inline-block"> - <small class="topic-stats"> - <span>[[category:posts]]</span> - <strong><span id="topic-post-count" class="human-readable-number" title="{postcount}">{postcount}</span></strong> | - <span>[[category:views]]</span> - <strong><span class="human-readable-number" title="{viewcount}">{viewcount}</span></strong> | - <span>[[category:browsing]]</span> - </small> - <div class="thread_active_users active-users inline-block"></div> - </div> - <div class="topic-main-buttons pull-right inline-block"> - <button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button> - <div class="btn-group thread-tools hide"> - <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button> - <ul class="dropdown-menu"> - <li><a href="#" class="pin_thread"><i class="fa fa-thumb-tack"></i> [[topic:thread_tools.pin]]</a></li> - <li><a href="#" class="lock_thread"><i class="fa fa-lock"></i> [[topic:thread_tools.lock]]</a></li> - <li class="divider"></li> - <li><a href="#" class="move_thread"><i class="fa fa-arrows"></i> [[topic:thread_tools.move]]</a></li> - <li class="divider"></li> - <li><a href="#" class="delete_thread"><span class="text-error"><i class="fa fa-trash-o"></i> [[topic:thread_tools.delete]]</span></a></li> - </ul> + + <div id="content_{posts.pid}" class="post-content" itemprop="text">{posts.content}</div> + <!-- IF posts.signature --> + <div class="post-signature">{posts.signature}</div> + <!-- ENDIF posts.signature --> + + <div class="post-info"> + <span class="pull-left"> + [[topic:reputation]]: <i class='fa fa-star'></i> <span class='formatted-number'>{posts.user_rep}</span> | [[topic:posts]]: <i class='fa fa-pencil'></i> <span class='formatted-number'>{posts.user_postcount}</span> + {posts.additional_profile_info} + </span> + <span class="pull-right"> + [[category:posted]] <span class="relativeTimeAgo timeago" title="{posts.relativeTime}"></span> + <!-- IF posts.editor --> + <span>| [[category:last_edited_by]] <strong><a href="/user/{posts.editorslug}">{posts.editorname}</a></strong></span> + <span class="timeago" title="{posts.relativeEditTime}"></span> + <!-- ENDIF posts.editor --> + </span> + <div style="clear:both;"></div> </div> </div> - <div style="clear:both;"></div> - </li> - <!-- ENDIF @first --> - <!-- END posts --> - </ul> - - <div class="well col-md-11 col-xs-12 pull-right hide"> - <div class="topic-main-buttons pull-right inline-block hide"> - <div class="loading-indicator" done="0" style="display:none;"> - [[topic:loading]] <span class="hidden-xs" style="display:inline!important;">[[topic:more_posts]]</span> <i class="fa fa-refresh fa-spin"></i> </div> - <button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button> - <div class="btn-group thread-tools hide"> - <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button> - <ul class="dropdown-menu"> - <li><a href="#" class="pin_thread"><i class="fa fa-thumb-tack"></i> [[topic:thread_tools.pin]]</a></li> - <li><a href="#" class="lock_thread"><i class="fa fa-lock"></i> [[topic:thread_tools.lock]]</a></li> - <li class="divider"></li> - <li><a href="#" class="move_thread"><i class="fa fa-arrows"></i> [[topic:thread_tools.move]]</a></li> - <li class="divider"></li> - <li><a href="#" class="delete_thread"><span class="text-error"><i class="fa fa-trash-o"></i> [[topic:thread_tools.delete]]</span></a></li> - </ul> + </li> + + <!-- IF @first --> + <li class="well post-bar"> + <div class="inline-block"> + <small class="topic-stats"> + <span>[[category:posts]]</span> + <strong><span id="topic-post-count" class="human-readable-number" title="{postcount}">{postcount}</span></strong> | + <span>[[category:views]]</span> + <strong><span class="human-readable-number" title="{viewcount}">{viewcount}</span></strong> | + <span>[[category:browsing]]</span> + </small> + <div class="thread_active_users active-users inline-block"></div> + </div> + <div class="topic-main-buttons pull-right inline-block"> + <button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button> + <div class="btn-group thread-tools hide"> + <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button> + <ul class="dropdown-menu"> + <li><a href="#" class="pin_thread"><i class="fa fa-thumb-tack"></i> [[topic:thread_tools.pin]]</a></li> + <li><a href="#" class="lock_thread"><i class="fa fa-lock"></i> [[topic:thread_tools.lock]]</a></li> + <li class="divider"></li> + <li><a href="#" class="move_thread"><i class="fa fa-arrows"></i> [[topic:thread_tools.move]]</a></li> + <li class="divider"></li> + <li><a href="#" class="delete_thread"><span class="text-error"><i class="fa fa-trash-o"></i> [[topic:thread_tools.delete]]</span></a></li> + </ul> + </div> </div> + <div style="clear:both;"></div> + </li> + <!-- ENDIF @first --> + <!-- END posts --> + </ul> + + <div class="well col-md-11 col-xs-12 pull-right hide"> + <div class="topic-main-buttons pull-right inline-block hide"> + <div class="loading-indicator" done="0" style="display:none;"> + [[topic:loading]] <span class="hidden-xs hidden-sm" style="display:inline!important;">[[topic:more_posts]]</span> <i class="fa fa-refresh fa-spin"></i> + </div> + <button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button> + <div class="btn-group thread-tools hide"> + <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button> + <ul class="dropdown-menu"> + <li><a href="#" class="pin_thread"><i class="fa fa-thumb-tack"></i> [[topic:thread_tools.pin]]</a></li> + <li><a href="#" class="lock_thread"><i class="fa fa-lock"></i> [[topic:thread_tools.lock]]</a></li> + <li class="divider"></li> + <li><a href="#" class="move_thread"><i class="fa fa-arrows"></i> [[topic:thread_tools.move]]</a></li> + <li class="divider"></li> + <li><a href="#" class="delete_thread"><span class="text-error"><i class="fa fa-trash-o"></i> [[topic:thread_tools.delete]]</span></a></li> + </ul> </div> - <div style="clear:both;"></div> </div> + <div style="clear:both;"></div> + </div> - <div id="move_thread_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Move Topic" 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>[[topic:move_topic]]</h3> - </div> - <div class="modal-body"> - <p id="categories-loading"><i class="fa fa-spin fa-refresh"></i> [[topic:load_categories]]</p> - <ul class="category-list"></ul> - <p> - [[topic:disabled_categories_note]] - </p> - <div id="move-confirm" style="display: none;"> - <hr /> - <div class="alert alert-info">[[topic:topic_will_be_moved_to]] <strong><span id="confirm-category-name"></span></strong></div> - </div> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal" id="move_thread_cancel">[[global:buttons.close]]</button> - <button type="button" class="btn btn-primary" id="move_thread_commit" disabled>[[topic:confirm_move]]</button> + <div id="move_thread_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Move Topic" 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>[[topic:move_topic]]</h3> + </div> + <div class="modal-body"> + <p id="categories-loading"><i class="fa fa-spin fa-refresh"></i> [[topic:load_categories]]</p> + <ul class="category-list"></ul> + <p> + [[topic:disabled_categories_note]] + </p> + <div id="move-confirm" style="display: none;"> + <hr /> + <div class="alert alert-info">[[topic:topic_will_be_moved_to]] <strong><span id="confirm-category-name"></span></strong></div> </div> </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal" id="move_thread_cancel">[[global:buttons.close]]</button> + <button type="button" class="btn btn-primary" id="move_thread_commit" disabled>[[topic:confirm_move]]</button> + </div> </div> </div> - </div> -</div> + +</div> \ No newline at end of file diff --git a/src/categories.js b/src/categories.js index 538bc0d5c3..2da43b1dd7 100644 --- a/src/categories.js +++ b/src/categories.js @@ -229,7 +229,7 @@ var db = require('./database.js'), return; } - posts.getPostSummaryByPids(pids, function(err, postData) { + posts.getPostSummaryByPids(pids, true, function(err, postData) { if (postData.length > count) { postData = postData.slice(0, count); } @@ -288,7 +288,10 @@ var db = require('./database.js'), Categories.getCategoryData = function(cid, callback) { db.exists('category:' + cid, function(err, exists) { if (exists) { - db.getObject('category:' + cid, callback); + db.getObject('category:' + cid, function(err, data) { + data.background = data.image ? 'url(' + data.image + ')' : data.bgColor; + callback(err, data); + }); } else { callback(new Error('No category found!')); } diff --git a/src/database/mongo.js b/src/database/mongo.js index 9408aff75a..d6af3bc0b2 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -46,7 +46,7 @@ return; } if(collection) { - collection.ensureIndex({_key :1, setName:1}, {background:true}, function(err, name){ + collection.ensureIndex({_key :1}, {background:true}, function(err, name){ if(err) { winston.error("Error creating index " + err.message); } @@ -73,6 +73,39 @@ }); } + // + // helper functions + // + function removeHiddenFields(item) { + if(item) { + if(item._id) { + delete item._id; + } + if(item._key) { + delete item._key; + } + } + return item; + } + + function findItem(data, key) { + if(!data) { + return null; + } + + for(var i=0; i<data.length; ++i) { + if(data[i]._key === key) { + var item = data.splice(i, 1); + if(item && item.length) { + return item[0]; + } else { + return null; + } + } + } + return null; + } + // // Exported functions @@ -155,8 +188,7 @@ stats.raw = JSON.stringify(stats, null, 4); stats.mongo = true; - //remove this when andrew adds in undefined checking to templates - stats.redis = false; + callback(err, stats); }); @@ -165,7 +197,7 @@ // key module.exists = function(key, callback) { - db.collection('objects').findOne({$or:[{_key:key}, {setName:key}]}, function(err, item) { + db.collection('objects').findOne({_key:key}, function(err, item) { callback(err, item !== undefined && item !== null); }); } @@ -180,16 +212,8 @@ } } - if(result === 0) { - db.collection('objects').remove({setName:key}, function(err, result) { - if(callback) { - callback(err, result); - } - }); - } else { - if(callback) { - callback(null, result); - } + if(callback) { + callback(null, result); } }); } @@ -210,21 +234,6 @@ } //hashes - function removeHiddenFields(item) { - if(item) { - if(item._id) { - delete item._id; - } - if(item._key) { - delete item._key; - } - if(item.setName) { - delete item.setName; - } - } - return item; - } - module.setObject = function(key, data, callback) { data['_key'] = key; db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err, result) { @@ -262,30 +271,10 @@ return callback(err); } - var returnData = [], - resultIndex = 0; - - - function findData(key) { - if(!data) { - return null; - } - - for(var i=0; i<data.length; ++i) { - if(data[i]._key === key) { - var item = data.splice(i, 1); - if(item && item.length) { - return item[0]; - } else { - return null; - } - } - } - return null; - } + var returnData = []; for(var i=0; i<keys.length; ++i) { - returnData.push(findData(keys[i])); + returnData.push(findItem(data, keys[i])); } callback(err, returnData); @@ -505,15 +494,18 @@ value:value }; - data.setName = key; - module.setObject(key + ':' + value, data, callback); + db.collection('objects').update({_key:key, value:value}, {$set:data}, {upsert:true, w: 1}, function(err, result) { + if(callback) { + callback(err, result); + } + }); } module.sortedSetRemove = function(key, value, callback) { if(value !== null && value !== undefined) { value = value.toString(); } - db.collection('objects').remove({setName:key, value:value}, function(err, result) { + db.collection('objects').remove({_key:key, value:value}, function(err, result) { if(callback) { callback(err, result); } @@ -521,7 +513,7 @@ } function getSortedSetRange(key, start, stop, sort, callback) { - db.collection('objects').find({setName:key}, {fields:{value:1}}) + db.collection('objects').find({_key:key}, {fields:{value:1}}) .limit(stop - start + 1) .skip(start) .sort({score: sort}) @@ -557,7 +549,7 @@ stop = args[5]; - db.collection('objects').find({setName:key, score: {$gt:min, $lt:max}}, {fields:{value:1}}) + db.collection('objects').find({_key:key, score: {$gte:min, $lte:max}}, {fields:{value:1}}) .limit(stop - start + 1) .skip(start) .sort({score: -1}) @@ -576,7 +568,7 @@ } module.sortedSetCount = function(key, min, max, callback) { - db.collection('objects').count({setName:key, score: {$gt:min, $lt:max}}, function(err, count) { + db.collection('objects').count({_key:key, score: {$gte:min, $lte:max}}, function(err, count) { if(err) { return callback(err); } @@ -609,7 +601,7 @@ if(value !== null && value !== undefined) { value = value.toString(); } - db.collection('objects').findOne({setName:key, value: value}, {fields:{score:1}}, function(err, result) { + db.collection('objects').findOne({_key:key, value: value}, {fields:{score:1}}, function(err, result) { if(err) { return callback(err); } @@ -625,22 +617,17 @@ if(value !== null && value !== undefined) { value = value.toString(); } - db.collection('objects').find({setName:{$in:keys}, value: value}).toArray(function(err, result) { + db.collection('objects').find({_key:{$in:keys}, value: value}).toArray(function(err, result) { if(err) { return callback(err); } var returnData = [], - resultIndex = 0; + item; for(var i=0; i<keys.length; ++i) { - - if(result && resultIndex < result.length && keys[i] === result[resultIndex].setName) { - returnData.push(result[resultIndex].score); - ++resultIndex; - } else { - returnData.push(null); - } + item = findItem(result, keys[i]); + returnData.push(item ? item.score : null); } callback(null, returnData); diff --git a/src/database/redis.js b/src/database/redis.js index b3a27ef895..d24573384b 100644 --- a/src/database/redis.js +++ b/src/database/redis.js @@ -168,8 +168,6 @@ } redisData.raw = JSON.stringify(redisData, null, 4); redisData.redis = true; - //remove this when andrew adds in undefined checking to templates - redisData.mongo = false; callback(null, redisData); }); diff --git a/src/install.js b/src/install.js index 601fdff077..ba59ac4826 100644 --- a/src/install.js +++ b/src/install.js @@ -104,12 +104,12 @@ var async = require('async'), } }, function (next) { - var success = function(err, config) { + var success = function (err, config) { if (!config) { return next(new Error('aborted')); } - function dbQuestionsSuccess(err, databaseConfig) { + var dbQuestionsSuccess = function (err, databaseConfig) { if (!databaseConfig) { return next(new Error('aborted')); } @@ -135,7 +135,7 @@ var async = require('async'), config.bcrypt_rounds = 12; config.upload_path = '/public/uploads'; - config.use_port = (config.use_port.slice(0, 1) === 'y') ? true : false; + config.use_port = config.use_port.slice(0, 1) === 'y'; var urlObject = url.parse(config.base_url), relative_path = (urlObject.pathname && urlObject.pathname.length > 1) ? urlObject.pathname : '', @@ -152,12 +152,20 @@ var async = require('async'), install.save(server_conf, client_conf, function(err) { require('./database').init(next); }); - } + }; if(config.database === 'redis') { - prompt.get(install.redisQuestions, dbQuestionsSuccess); + if (config['redis:host'] && config['redis:port']) { + dbQuestionsSuccess(null, config); + } else { + prompt.get(install.redisQuestions, dbQuestionsSuccess); + } } else if(config.database === 'mongo') { - prompt.get(install.mongoQuestions, dbQuestionsSuccess); + if (config['mongo:host'] && config['mongo:port']) { + dbQuestionsSuccess(null, config); + } else { + prompt.get(install.mongoQuestions, dbQuestionsSuccess); + } } else { return next(new Error('unknown database : ' + config.database)); } @@ -173,9 +181,9 @@ var async = require('async'), } else { // Use provided values, fall back to defaults var config = {}, - question, x, numQ; - for(x=0,numQ=install.questions.length;x<numQ;x++) { - question = install.questions[x]; + question, x, numQ, allQuestions = install.questions.concat(install.redisQuestions).concat(install.mongoQuestions); + for(x=0,numQ=allQuestions.length;x<numQ;x++) { + question = allQuestions[x]; config[question.name] = install.values[question.name] || question['default'] || ''; } success(null, config); diff --git a/src/meta.js b/src/meta.js index fa50858515..f0573d5d64 100644 --- a/src/meta.js +++ b/src/meta.js @@ -144,27 +144,19 @@ var fs = require('fs'), }; Meta.title = { - build: function (urlFragment, current_user, callback) { - var self = this, - user = require('./user'); + build: function (urlFragment, callback) { + var user = require('./user'); - async.parallel({ - title: function (next) { - self.parseFragment(urlFragment, next); - }, - notifCount: function (next) { - user.notifications.getUnreadCount(current_user, next); - } - }, function (err, values) { + Meta.title.parseFragment(urlFragment, function(err, title) { var title; if (err) { title = Meta.config.title || 'NodeBB'; } else { - title = (values.title ? values.title + ' | ' : '') + (Meta.config.title || 'NodeBB'); + title = (title ? title + ' | ' : '') + (Meta.config.title || 'NodeBB'); } - callback(null, title, values.notifCount); + callback(null, title); }); }, parseFragment: function (urlFragment, callback) { diff --git a/src/postTools.js b/src/postTools.js index e31fa70552..506868ff95 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -48,10 +48,14 @@ var db = require('./database'), } function hasEnoughRep(next) { - user.getUserField(uid, 'reputation', function(err, reputation) { - if (err) return next(null, false); - next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_content'], 10)); - }); + if (parseInt(meta.config['privileges:disabled'], 10)) { + return next(null, false); + } else { + user.getUserField(uid, 'reputation', function(err, reputation) { + if (err) return next(null, false); + next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_content'], 10)); + }); + } } async.parallel([getThreadPrivileges, isOwnPost, hasEnoughRep], function(err, results) { diff --git a/src/posts.js b/src/posts.js index c496afdd99..e17e22f300 100644 --- a/src/posts.js +++ b/src/posts.js @@ -134,45 +134,30 @@ var db = require('./database'), callback(new Error('reply-error'), null); } - async.parallel([ - function(next) { - topics.markUnRead(tid, function(err) { - if(err) { - return next(err); - } - topics.markAsRead(tid, uid); - next(); - }); - }, - function(next) { - topics.pushUnreadCount(null, next); - }, - function(next) { - Posts.getCidByPid(postData.pid, function(err, cid) { - if(err) { - return next(err); - } + Posts.getCidByPid(postData.pid, function(err, cid) { + if(err) { + return callback(err, null); + } - db.delete('cid:' + cid + ':read_by_uid'); - next(); - }); - }, - function(next) { - threadTools.notifyFollowers(tid, uid); - next(); - }, - function(next) { - Posts.addUserInfoToPost(postData, function(err) { - if(err) { - return next(err); - } - next(); - }); + db.delete('cid:' + cid + ':read_by_uid'); + }); + + topics.markAsUnreadForAll(tid, function(err) { + if(err) { + return callback(err, null); } - ], function(err, results) { + + topics.markAsRead(tid, uid); + topics.pushUnreadCount(); + }); + + threadTools.notifyFollowers(tid, uid); + + Posts.addUserInfoToPost(postData, function(err) { if(err) { return callback(err, null); } + callback(null, postData); }); }); @@ -205,7 +190,7 @@ var db = require('./database'), Posts.addUserInfoToPost = function(post, callback) { user.getUserFields(post.uid, ['username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], function(err, userData) { if (err) { - return callback(); + return callback(err); } postTools.parseSignature(userData.signature, function(err, signature) { @@ -242,7 +227,7 @@ var db = require('./database'), }); }; - Posts.getPostSummaryByPids = function(pids, callback) { + Posts.getPostSummaryByPids = function(pids, stripTags, callback) { var posts = []; @@ -283,10 +268,17 @@ var db = require('./database'), function(postData, next) { if (postData.content) { postTools.parse(postData.content, function(err, content) { - if (!err) { + if(err) { + return next(err); + } + + if(stripTags) { postData.content = utils.strip_tags(content); + } else { + postData.content = content; } - next(err, postData); + + next(null, postData); }); } else { next(null, postData); @@ -504,7 +496,7 @@ var db = require('./database'), if (err) return callback(err, null); - Posts.getPostSummaryByPids(pids, function(err, posts) { + Posts.getPostSummaryByPids(pids, false, function(err, posts) { if (err) return callback(err, null); diff --git a/src/routes/admin.js b/src/routes/admin.js index 2831787434..495b341a31 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -10,7 +10,8 @@ var nconf = require('nconf'), pkg = require('./../../package.json'), categories = require('./../categories'), meta = require('../meta'), - plugins = require('../plugins'); + plugins = require('../plugins'), + utils = require('./../../public/src/utils.js'); @@ -97,6 +98,53 @@ var nconf = require('nconf'), }); }); + app.post('/category/uploadpicture', function(req, res) { + if (!req.user) + return res.redirect('/403'); + + var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; + + if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) { + res.send({ + error: 'Allowed image types are png, jpg and gif!' + }); + return; + } + + var tempPath = req.files.userPhoto.path; + var extension = path.extname(req.files.userPhoto.name); + + if (!extension) { + res.send({ + error: 'Error uploading file! Error : Invalid extension!' + }); + return; + } + + var filename = 'category' + utils.generateUUID() + extension; + var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename); + + winston.info('Attempting upload to: ' + uploadPath); + + var is = fs.createReadStream(tempPath); + var os = fs.createWriteStream(uploadPath); + + is.on('end', function () { + fs.unlinkSync(tempPath); + console.log(nconf.get('upload_url') + filename); + res.json({ + path: nconf.get('upload_url') + filename + }); + }); + + os.on('error', function (err) { + fs.unlinkSync(tempPath); + winston.err(err); + }); + + is.pipe(os); + }); + app.post('/uploadlogo', function(req, res) { if (!req.user) diff --git a/src/routes/api.js b/src/routes/api.js index 0bebd9f975..0ba2af2a44 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -2,6 +2,7 @@ var path = require('path'), nconf = require('nconf'), async = require('async'), + db = require('../database'), user = require('../user'), auth = require('./authentication'), topics = require('../topics'), @@ -214,14 +215,18 @@ var path = require('path'), }); app.get('/search', function (req, res) { - return res.json({ - show_no_topics: 'hide', - show_no_posts: 'hide', - show_results: 'hide', - search_query: '', - posts: [], - topics: [] - }); + if (req.user && req.user.uid) { + return res.json({ + show_no_topics: 'hide', + show_no_posts: 'hide', + show_results: 'hide', + search_query: '', + posts: [], + topics: [] + }); + } else { + res.send(403); + } }); app.get('/search/:term', function (req, res, next) { @@ -232,7 +237,7 @@ var path = require('path'), return callback(err, null); } - posts.getPostSummaryByPids(pids, function (err, posts) { + posts.getPostSummaryByPids(pids, false, function (err, posts) { if (err){ return callback(err, null); } @@ -253,20 +258,24 @@ var path = require('path'), }); } - async.parallel([searchPosts, searchTopics], function (err, results) { - if (err) { - return next(); - } + if (req.user && req.user.uid) { + async.parallel([searchPosts, searchTopics], function (err, results) { + if (err) { + return next(); + } - return res.json({ - show_no_topics: results[1].length ? 'hide' : '', - show_no_posts: results[0].length ? 'hide' : '', - show_results: '', - search_query: req.params.term, - posts: results[0], - topics: results[1] + return res.json({ + show_no_topics: results[1].length ? 'hide' : '', + show_no_posts: results[0].length ? 'hide' : '', + show_results: '', + search_query: req.params.term, + posts: results[0], + topics: results[1] + }); }); - }); + } else { + res.send(403); + } }); app.get('/reset', function (req, res) { diff --git a/src/routes/debug.js b/src/routes/debug.js index d089a48b2c..14f87fa8a7 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -78,6 +78,7 @@ var DebugRoute = function(app) { }); }); }); + }); }; diff --git a/src/routes/user.js b/src/routes/user.js index 3eb0acd6b8..a978e50eee 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -102,7 +102,7 @@ var fs = require('fs'), if (!req.user) return res.redirect('/403'); - var uploadSize = meta.config.maximumProfileImageSize || 256; + var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; if (req.files.userPhoto.size > uploadSize * 1024) { res.send({ @@ -147,18 +147,21 @@ var fs = require('fs'), return; } - var filename = uid + '-profileimg' + extension; + var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10); + + var filename = uid + '-profileimg' + (convertToPNG ? '.png' : extension); var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename); winston.info('Attempting upload to: ' + uploadPath); var is = fs.createReadStream(tempPath); var os = fs.createWriteStream(uploadPath); + var im = require('node-imagemagick'); is.on('end', function () { fs.unlinkSync(tempPath); - require('node-imagemagick').crop({ + im.crop({ srcPath: uploadPath, dstPath: uploadPath, width: 128, @@ -177,6 +180,22 @@ var fs = require('fs'), user.setUserField(uid, 'uploadedpicture', imageUrl); user.setUserField(uid, 'picture', imageUrl); + if (convertToPNG) { + im.convert([uploadPath, 'png:-'], + function(err, stdout){ + if (err) { + winston.err(err); + res.send({ + error: 'Unable to convert image to PNG.' + }); + return; + } + + fs.writeFileSync(uploadPath, stdout, 'binary'); + }); + } + + res.json({ path: imageUrl }); diff --git a/src/threadTools.js b/src/threadTools.js index ed32f795be..ba4ec6fe49 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -33,10 +33,14 @@ var db = require('./database'), }); }, hasEnoughRep: function(next) { - user.getUserField(uid, 'reputation', function(err, reputation) { - if (err) return next(null, false); - next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_topic'], 10)); - }); + if (parseInt(meta.config['privileges:disabled'], 10)) { + return next(null, false); + } else { + user.getUserField(uid, 'reputation', function(err, reputation) { + if (err) return next(null, false); + next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_topic'], 10)); + }); + } } }, function(err, results) { callback(err, !results ? undefined : { diff --git a/src/topics.js b/src/topics.js index f5a496bc77..8e5f4a3f43 100644 --- a/src/topics.js +++ b/src/topics.js @@ -701,7 +701,7 @@ var async = require('async'), }); } - Topics.markUnRead = function(tid, callback) { + Topics.markAsUnreadForAll = function(tid, callback) { db.delete('tid:' + tid + ':read_by_uid', callback); } diff --git a/src/upgrade.js b/src/upgrade.js index d3b938743e..dac9e4a015 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -237,7 +237,15 @@ Upgrade.upgradeRedis = function(callback) { function updateKeyToHash(key, next) { RDB.get(key, function(err, value) { - RDB.hset('global', newKeys[key], value, next); + if(err) { + return next(err); + } + + if(value === null) { + RDB.hset('global', newKeys[key], initialValues[key], next); + } else { + RDB.hset('global', newKeys[key], value, next); + } }); } @@ -270,6 +278,19 @@ Upgrade.upgradeRedis = function(callback) { 'totalpostcount':'postCount' }; + var initialValues = { + 'global:next_user_id': 1, + 'next_topic_id': 0, + 'next_gid': 1, + 'notifications:next_nid': 0, + 'global:next_category_id': 12, + 'global:next_message_id': 0, + 'global:next_post_id': 0, + 'usercount': 1, + 'totaltopiccount': 0, + 'totalpostcount': 0 + }; + async.each(keys, updateKeyToHash, function(err) { if(err) { return next(err); diff --git a/src/websockets.js b/src/websockets.js index 8a76ca3402..a355ec873f 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -677,7 +677,7 @@ websockets.init = function(io) { }); }); - socket.on('getChatMessages', function(data, callback) { + socket.on('api:chats.get', function(data, callback) { var touid = data.touid; Messaging.getMessages(uid, touid, function(err, messages) { if (err) @@ -687,7 +687,7 @@ websockets.init = function(io) { }); }); - socket.on('sendChatMessage', function(data) { + socket.on('api:chats.send', function(data) { var touid = data.touid; if (touid === uid || uid === 0) { @@ -696,9 +696,15 @@ websockets.init = function(io) { var msg = utils.strip_tags(data.message); - user.getUserField(uid, 'username', function(err, username) { + user.getMultipleUserFields([uid, touid], ['username'], function(err, usersData) { + if(err) { + return; + } + var finalMessage = username + ' : ' + msg, - notifText = 'New message from <strong>' + username + '</strong>'; + notifText = 'New message from <strong>' + username + '</strong>', + username = usersData[0].username, + toUsername = usersData[1].username; if (!isUserOnline(touid)) { notifications.create(notifText, 'javascript:app.openChat('' + username + '', ' + uid + ');', 'notification_' + uid + '_' + touid, function(nid) { @@ -715,7 +721,7 @@ websockets.init = function(io) { numSockets = userSockets[touid].length; for (var x = 0; x < numSockets; ++x) { - userSockets[touid][x].emit('chatMessage', { + userSockets[touid][x].emit('event:chats.receive', { fromuid: uid, username: username, message: finalMessage, @@ -729,9 +735,9 @@ websockets.init = function(io) { numSockets = userSockets[uid].length; for (var x = 0; x < numSockets; ++x) { - userSockets[uid][x].emit('chatMessage', { + userSockets[uid][x].emit('event:chats.receive', { fromuid: touid, - username: username, + username: toUsername, message: 'You : ' + msg, timestamp: Date.now() }); @@ -1078,8 +1084,8 @@ websockets.init = function(io) { }); socket.on('api:meta.buildTitle', function(text, callback) { - meta.title.build(text, uid, function(err, title, numNotifications) { - callback(title, numNotifications); + meta.title.build(text, function(err, title) { + callback(title); }); });