diff --git a/README.md b/README.md
index 4fc6d9aeb9..9062b049ae 100644
--- a/README.md
+++ b/README.md
@@ -1,75 +1,75 @@
-#
-
-[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[![Build Status](https://travis-ci.org/NodeBB/NodeBB.svg?branch=master)](https://travis-ci.org/NodeBB/NodeBB)
-[![Dependency Status](https://david-dm.org/nodebb/nodebb.svg)](https://david-dm.org/nodebb/nodebb)
-[![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB)
-[![Documentation Status](https://readthedocs.org/projects/nodebb/badge/?version=latest)](https://readthedocs.org/projects/nodebb/?badge=latest)
-
-**NodeBB Forum Software** is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
-
-Additional functionality is enabled through the use of third-party plugins.
-
-* [Get NodeBB](http://www.nodebb.org/ "NodeBB")
-* [Demo & Meta Discussion](http://community.nodebb.org)
-* [Documentation & Installation Instructions](http://docs.nodebb.org)
-* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
-* [NodeBB Blog](http://blog.nodebb.org)
-* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
-* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
-* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
-
-## Screenshots
-
-[![](http://i.imgur.com/VCoOFyqb.png)](http://i.imgur.com/VCoOFyq.png)
-[![](http://i.imgur.com/FLOUuIqb.png)](http://i.imgur.com/FLOUuIq.png)
-[![](http://i.imgur.com/Ud1LrfIb.png)](http://i.imgur.com/Ud1LrfI.png)
-[![](http://i.imgur.com/h6yZ66sb.png)](http://i.imgur.com/h6yZ66s.png)
-[![](http://i.imgur.com/o90kVPib.png)](http://i.imgur.com/o90kVPi.png)
-[![](http://i.imgur.com/AaRRrU2b.png)](http://i.imgur.com/AaRRrU2.png)
-[![](http://i.imgur.com/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png)
-[![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg)
-
-[![](http://i.imgur.com/8OLssij.png)](http://i.imgur.com/8OLssij.png)
-[![](http://i.imgur.com/JKOc0LZ.png)](http://i.imgur.com/JKOc0LZ.png)
-
-## How can I follow along/contribute?
-
-* Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/NodeBB/NodeBB/wiki/Version-History-%26-Roadmap)
-* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
-* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
-* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
-* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
-
-## Requirements
-
-NodeBB requires the following software to be installed:
-
-* A version of Node.js at least 0.10 or greater
-* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
-* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
-
-## Installation
-
-[Please refer to platform-specific installation documentation](http://docs.nodebb.org/en/latest/installing/os.html)
-
-## Securing NodeBB
-
-It is important to ensure that your NodeBB and database servers are secured. Bear these points in mind:
-
-1. While some distributions set up Redis with a more restrictive configuration, Redis by default listens to all interfaces, which is especially dangerous when a server is open to the public. Some suggestions:
- * Set `bind_address` to `127.0.0.1` so as to restrict access to the local machine only
- * Use `requirepass` to secure Redis behind a password (preferably a long one)
- * Familiarise yourself with [Redis Security](http://redis.io/topics/security)
-2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
- * e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
-
-## Upgrading NodeBB
-
-Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/en/latest/upgrading/index.html)
-
-## License
-
-NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
-
+#
+
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Build Status](https://travis-ci.org/NodeBB/NodeBB.svg?branch=master)](https://travis-ci.org/NodeBB/NodeBB)
+[![Dependency Status](https://david-dm.org/nodebb/nodebb.svg)](https://david-dm.org/nodebb/nodebb)
+[![Code Climate](https://codeclimate.com/github/NodeBB/NodeBB/badges/gpa.svg)](https://codeclimate.com/github/NodeBB/NodeBB)
+[![Documentation Status](https://readthedocs.org/projects/nodebb/badge/?version=latest)](https://readthedocs.org/projects/nodebb/?badge=latest)
+
+**NodeBB Forum Software** is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB is compatible down to IE8 and has many modern features out of the box such as social network integration and streaming discussions.
+
+Additional functionality is enabled through the use of third-party plugins.
+
+* [Get NodeBB](http://www.nodebb.org/ "NodeBB")
+* [Demo & Meta Discussion](http://community.nodebb.org)
+* [Documentation & Installation Instructions](http://docs.nodebb.org)
+* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
+* [NodeBB Blog](http://blog.nodebb.org)
+* [Join us on IRC](https://kiwiirc.com/client/irc.freenode.net/nodebb) - #nodebb on Freenode
+* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
+* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
+
+## Screenshots
+
+[![](http://i.imgur.com/VCoOFyqb.png)](http://i.imgur.com/VCoOFyq.png)
+[![](http://i.imgur.com/FLOUuIqb.png)](http://i.imgur.com/FLOUuIq.png)
+[![](http://i.imgur.com/Ud1LrfIb.png)](http://i.imgur.com/Ud1LrfI.png)
+[![](http://i.imgur.com/h6yZ66sb.png)](http://i.imgur.com/h6yZ66s.png)
+[![](http://i.imgur.com/o90kVPib.png)](http://i.imgur.com/o90kVPi.png)
+[![](http://i.imgur.com/AaRRrU2b.png)](http://i.imgur.com/AaRRrU2.png)
+[![](http://i.imgur.com/LmHtPhob.png)](http://i.imgur.com/LmHtPho.png)
+[![](http://i.imgur.com/paiJPJkb.jpg)](http://i.imgur.com/paiJPJk.jpg)
+
+[![](http://i.imgur.com/8OLssij.png)](http://i.imgur.com/8OLssij.png)
+[![](http://i.imgur.com/JKOc0LZ.png)](http://i.imgur.com/JKOc0LZ.png)
+
+## How can I follow along/contribute?
+
+* Our feature roadmap is hosted on the project wiki's [Version History / Roadmap](https://github.com/NodeBB/NodeBB/wiki/Version-History-%26-Roadmap)
+* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
+* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
+* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
+* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
+
+## Requirements
+
+NodeBB requires the following software to be installed:
+
+* A version of Node.js at least 0.10 or greater
+* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
+* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
+
+## Installation
+
+[Please refer to platform-specific installation documentation](http://docs.nodebb.org/en/latest/installing/os.html)
+
+## Securing NodeBB
+
+It is important to ensure that your NodeBB and database servers are secured. Bear these points in mind:
+
+1. While some distributions set up Redis with a more restrictive configuration, Redis by default listens to all interfaces, which is especially dangerous when a server is open to the public. Some suggestions:
+ * Set `bind_address` to `127.0.0.1` so as to restrict access to the local machine only
+ * Use `requirepass` to secure Redis behind a password (preferably a long one)
+ * Familiarise yourself with [Redis Security](http://redis.io/topics/security)
+2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
+ * e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
+
+## Upgrading NodeBB
+
+Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/en/latest/upgrading/index.html)
+
+## License
+
+NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
+
Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org.
\ No newline at end of file
diff --git a/app.js b/app.js
index 0a963230b0..0f5de4bfe9 100644
--- a/app.js
+++ b/app.js
@@ -1,416 +1,416 @@
-/*
- NodeBB - A better forum platform for the modern web
- https://github.com/NodeBB/NodeBB/
- Copyright (C) 2013-2014 NodeBB Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-"use strict";
-/*global require, global, process*/
-
-var nconf = require('nconf');
-nconf.argv().env('__');
-
-var fs = require('fs'),
- os = require('os'),
- url = require('url'),
- async = require('async'),
- semver = require('semver'),
- winston = require('winston'),
- colors = require('colors'),
- path = require('path'),
- pkg = require('./package.json'),
- utils = require('./public/src/utils.js');
-
-global.env = process.env.NODE_ENV || 'production';
-
-winston.remove(winston.transports.Console);
-winston.add(winston.transports.Console, {
- colorize: true,
- timestamp: function() {
- var date = new Date();
- return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
- },
- level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose')
-});
-
-if(os.platform() === 'linux') {
- 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?');
- }
- });
-}
-
-// Alternate configuration file support
-var configFile = path.join(__dirname, '/config.json'),
- configExists;
-
-if (nconf.get('config')) {
- configFile = path.resolve(__dirname, nconf.get('config'));
-}
-configExists = fs.existsSync(configFile);
-
-if (!nconf.get('setup') && !nconf.get('install') && !nconf.get('upgrade') && !nconf.get('reset') && configExists) {
- start();
-} else if (nconf.get('setup') || nconf.get('install')) {
- setup();
-} else if (!configExists) {
- require('./install/web').install(nconf.get('port'));
-} else if (nconf.get('upgrade')) {
- upgrade();
-} else if (nconf.get('reset')) {
- reset();
-}
-
-function loadConfig() {
- nconf.file({
- file: configFile
- });
-
- nconf.defaults({
- base_dir: __dirname,
- themes_path: path.join(__dirname, 'node_modules'),
- views_dir: path.join(__dirname, 'public/templates'),
- version: pkg.version
- });
-
- if (!nconf.get('isCluster')) {
- nconf.set('isPrimary', 'true');
- nconf.set('isCluster', 'false');
- }
-
- // Ensure themes_path is a full filepath
- nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
- nconf.set('core_templates_path', path.join(__dirname, 'src/views'));
- nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-vanilla/templates'));
-
- if (!process.send) {
- // If run using `node app`, log GNU copyright info along with server info
- winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-2014 NodeBB Inc.');
- winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
- winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
- winston.info('');
- }
-}
-
-function start() {
- loadConfig();
- var db = require('./src/database');
-
- // nconf defaults, if not set in config
- if (!nconf.get('upload_path')) {
- nconf.set('upload_path', '/public/uploads');
- }
- // Parse out the relative_url and other goodies from the configured URL
- var urlObject = url.parse(nconf.get('url'));
- var relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
- nconf.set('base_url', urlObject.protocol + '//' + urlObject.host);
- nconf.set('use_port', !!urlObject.port);
- nconf.set('relative_path', relativePath);
- nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || 4567);
- nconf.set('upload_url', '/uploads/');
-
- if (nconf.get('isPrimary') === 'true') {
- winston.info('Time: %s', (new Date()).toString());
- winston.info('Initializing NodeBB v%s', nconf.get('version'));
- winston.verbose('* using configuration stored in: %s', configFile);
-
- var host = nconf.get(nconf.get('database') + ':host'),
- storeLocation = host ? 'at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : '') : '';
-
- winston.verbose('* using %s store %s', nconf.get('database'), storeLocation);
- winston.verbose('* using themes stored in: %s', nconf.get('themes_path'));
- }
-
- process.on('SIGTERM', shutdown);
- process.on('SIGINT', shutdown);
- process.on('SIGHUP', restart);
- process.on('message', function(message) {
- if (typeof message !== 'object') {
- return;
- }
- var meta = require('./src/meta');
- var emitter = require('./src/emitter');
- switch (message.action) {
- case 'reload':
- meta.reload();
- break;
- case 'js-propagate':
- meta.js.cache = message.cache;
- meta.js.map = message.map;
- meta.js.hash = message.hash;
- emitter.emit('meta:js.compiled');
- winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid);
- break;
- case 'css-propagate':
- meta.css.cache = message.cache;
- meta.css.acpCache = message.acpCache;
- meta.css.hash = message.hash;
- emitter.emit('meta:css.compiled');
- winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid);
- break;
- case 'templates:compiled':
- emitter.emit('templates:compiled');
- break;
- }
- });
-
- process.on('uncaughtException', function(err) {
- winston.error(err.stack);
- console.log(err.stack);
-
- require('./src/meta').js.killMinifier();
- shutdown(1);
- });
-
- async.waterfall([
- async.apply(db.init),
- async.apply(db.checkCompatibility),
- function(next) {
- require('./src/meta').configs.init(next);
- },
- function(next) {
- require('./src/meta').dependencies.check(next);
- },
- function(next) {
- require('./src/upgrade').check(next);
- },
- function(next) {
- var webserver = require('./src/webserver');
- require('./src/socket.io').init(webserver.server);
-
- if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
- require('./src/notifications').init();
- require('./src/user').startJobs();
- }
-
- webserver.listen();
- }
- ], function(err) {
- if (err) {
- switch(err.message) {
- case 'schema-out-of-date':
- winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
- winston.warn(' ./nodebb upgrade');
- break;
- case 'dependencies-out-of-date':
- winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:');
- winston.warn(' ./nodebb upgrade');
- break;
- default:
- if (err.stacktrace !== false) {
- winston.error(err.stack);
- } else {
- winston.error(err.message);
- }
- break;
- }
-
- // Either way, bad stuff happened. Abort start.
- process.exit();
- }
- });
-}
-
-function setup() {
- loadConfig();
-
- winston.info('NodeBB Setup Triggered via Command Line');
-
- var install = require('./src/install');
-
- process.stdout.write('\nWelcome to NodeBB!\n');
- process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
- process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
-
- install.setup(function (err, data) {
- var separator = ' ';
- if (process.stdout.columns > 10) {
- for(var x=0,cols=process.stdout.columns-10;x.
+*/
+
+"use strict";
+/*global require, global, process*/
+
+var nconf = require('nconf');
+nconf.argv().env('__');
+
+var fs = require('fs'),
+ os = require('os'),
+ url = require('url'),
+ async = require('async'),
+ semver = require('semver'),
+ winston = require('winston'),
+ colors = require('colors'),
+ path = require('path'),
+ pkg = require('./package.json'),
+ utils = require('./public/src/utils.js');
+
+global.env = process.env.NODE_ENV || 'production';
+
+winston.remove(winston.transports.Console);
+winston.add(winston.transports.Console, {
+ colorize: true,
+ timestamp: function() {
+ var date = new Date();
+ return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
+ },
+ level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose')
+});
+
+if(os.platform() === 'linux') {
+ 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?');
+ }
+ });
+}
+
+// Alternate configuration file support
+var configFile = path.join(__dirname, '/config.json'),
+ configExists;
+
+if (nconf.get('config')) {
+ configFile = path.resolve(__dirname, nconf.get('config'));
+}
+configExists = fs.existsSync(configFile);
+
+if (!nconf.get('setup') && !nconf.get('install') && !nconf.get('upgrade') && !nconf.get('reset') && configExists) {
+ start();
+} else if (nconf.get('setup') || nconf.get('install')) {
+ setup();
+} else if (!configExists) {
+ require('./install/web').install(nconf.get('port'));
+} else if (nconf.get('upgrade')) {
+ upgrade();
+} else if (nconf.get('reset')) {
+ reset();
+}
+
+function loadConfig() {
+ nconf.file({
+ file: configFile
+ });
+
+ nconf.defaults({
+ base_dir: __dirname,
+ themes_path: path.join(__dirname, 'node_modules'),
+ views_dir: path.join(__dirname, 'public/templates'),
+ version: pkg.version
+ });
+
+ if (!nconf.get('isCluster')) {
+ nconf.set('isPrimary', 'true');
+ nconf.set('isCluster', 'false');
+ }
+
+ // Ensure themes_path is a full filepath
+ nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
+ nconf.set('core_templates_path', path.join(__dirname, 'src/views'));
+ nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-vanilla/templates'));
+
+ if (!process.send) {
+ // If run using `node app`, log GNU copyright info along with server info
+ winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-2014 NodeBB Inc.');
+ winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
+ winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
+ winston.info('');
+ }
+}
+
+function start() {
+ loadConfig();
+ var db = require('./src/database');
+
+ // nconf defaults, if not set in config
+ if (!nconf.get('upload_path')) {
+ nconf.set('upload_path', '/public/uploads');
+ }
+ // Parse out the relative_url and other goodies from the configured URL
+ var urlObject = url.parse(nconf.get('url'));
+ var relativePath = urlObject.pathname !== '/' ? urlObject.pathname : '';
+ nconf.set('base_url', urlObject.protocol + '//' + urlObject.host);
+ nconf.set('use_port', !!urlObject.port);
+ nconf.set('relative_path', relativePath);
+ nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || 4567);
+ nconf.set('upload_url', '/uploads/');
+
+ if (nconf.get('isPrimary') === 'true') {
+ winston.info('Time: %s', (new Date()).toString());
+ winston.info('Initializing NodeBB v%s', nconf.get('version'));
+ winston.verbose('* using configuration stored in: %s', configFile);
+
+ var host = nconf.get(nconf.get('database') + ':host'),
+ storeLocation = host ? 'at ' + host + (host.indexOf('/') === -1 ? ':' + nconf.get(nconf.get('database') + ':port') : '') : '';
+
+ winston.verbose('* using %s store %s', nconf.get('database'), storeLocation);
+ winston.verbose('* using themes stored in: %s', nconf.get('themes_path'));
+ }
+
+ process.on('SIGTERM', shutdown);
+ process.on('SIGINT', shutdown);
+ process.on('SIGHUP', restart);
+ process.on('message', function(message) {
+ if (typeof message !== 'object') {
+ return;
+ }
+ var meta = require('./src/meta');
+ var emitter = require('./src/emitter');
+ switch (message.action) {
+ case 'reload':
+ meta.reload();
+ break;
+ case 'js-propagate':
+ meta.js.cache = message.cache;
+ meta.js.map = message.map;
+ meta.js.hash = message.hash;
+ emitter.emit('meta:js.compiled');
+ winston.verbose('[cluster] Client-side javascript and mapping propagated to worker %s', process.pid);
+ break;
+ case 'css-propagate':
+ meta.css.cache = message.cache;
+ meta.css.acpCache = message.acpCache;
+ meta.css.hash = message.hash;
+ emitter.emit('meta:css.compiled');
+ winston.verbose('[cluster] Stylesheets propagated to worker %s', process.pid);
+ break;
+ case 'templates:compiled':
+ emitter.emit('templates:compiled');
+ break;
+ }
+ });
+
+ process.on('uncaughtException', function(err) {
+ winston.error(err.stack);
+ console.log(err.stack);
+
+ require('./src/meta').js.killMinifier();
+ shutdown(1);
+ });
+
+ async.waterfall([
+ async.apply(db.init),
+ async.apply(db.checkCompatibility),
+ function(next) {
+ require('./src/meta').configs.init(next);
+ },
+ function(next) {
+ require('./src/meta').dependencies.check(next);
+ },
+ function(next) {
+ require('./src/upgrade').check(next);
+ },
+ function(next) {
+ var webserver = require('./src/webserver');
+ require('./src/socket.io').init(webserver.server);
+
+ if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
+ require('./src/notifications').init();
+ require('./src/user').startJobs();
+ }
+
+ webserver.listen();
+ }
+ ], function(err) {
+ if (err) {
+ switch(err.message) {
+ case 'schema-out-of-date':
+ winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
+ winston.warn(' ./nodebb upgrade');
+ break;
+ case 'dependencies-out-of-date':
+ winston.warn('One or more of NodeBB\'s dependent packages are out-of-date. Please run the following command to update them:');
+ winston.warn(' ./nodebb upgrade');
+ break;
+ default:
+ if (err.stacktrace !== false) {
+ winston.error(err.stack);
+ } else {
+ winston.error(err.message);
+ }
+ break;
+ }
+
+ // Either way, bad stuff happened. Abort start.
+ process.exit();
+ }
+ });
+}
+
+function setup() {
+ loadConfig();
+
+ winston.info('NodeBB Setup Triggered via Command Line');
+
+ var install = require('./src/install');
+
+ process.stdout.write('\nWelcome to NodeBB!\n');
+ process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
+ process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
+
+ install.setup(function (err, data) {
+ var separator = ' ';
+ if (process.stdout.columns > 10) {
+ for(var x=0,cols=process.stdout.columns-10;x 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true)) // Subfolder installs need this additional check
- ) {
- // Internal link
- var url = this.pathname.replace(RELATIVE_PATH + '/', '');
-
- // Special handling for urls with hashes
- if (window.location.pathname === this.pathname && this.hash.length) {
- window.location.hash = this.hash;
- } else {
- window.location.hash = '';
- if (ajaxify.go(url)) {
- e.preventDefault();
- }
- }
- } else if (window.location.pathname !== '/outgoing') {
- // External Link
- if (config.openOutgoingLinksInNewTab) {
- window.open(this.href, '_blank');
- e.preventDefault();
- } else if (config.useOutgoingLinksPage) {
- ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
- e.preventDefault();
- }
- }
- }
- });
- }
-
- if (window.history && window.history.pushState) {
- // Progressive Enhancement, ajaxify available only to modern browsers
- ajaxifyAnchors();
- }
-
- app.load();
- templates.cache['500'] = $('.tpl-500').html();
-
+"use strict";
+
+var ajaxify = ajaxify || {};
+
+$(document).ready(function() {
+
+ /*global app, templates, utils, socket, config, RELATIVE_PATH*/
+
+ var location = document.location || window.location,
+ rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''),
+ apiXHR = null,
+
+ translator;
+
+ // Dumb hack to fool ajaxify into thinking translator is still a global
+ // When ajaxify is migrated to a require.js module, then this can be merged into the "define" call
+ require(['translator'], function(_translator) {
+ translator = _translator;
+ });
+
+ $(window).on('popstate', function (ev) {
+ ev = ev.originalEvent;
+
+ if (ev !== null && ev.state && ev.state.url !== undefined) {
+ ajaxify.go(ev.state.url, function() {
+ $(window).trigger('action:popstate', {url: ev.state.url});
+ }, true);
+ }
+ });
+
+ ajaxify.currentPage = null;
+
+ ajaxify.go = function (url, callback, quiet, search) {
+ if (!socket.connected) {
+ if (ajaxify.reconnectAction) {
+ $(window).off('action:reconnected', ajaxify.reconnectAction);
+ }
+ ajaxify.reconnectAction = function(e) {
+ ajaxify.go(url, callback, quiet, search);
+ $(window).off(e);
+ }
+ $(window).on('action:reconnected', ajaxify.reconnectAction);
+ }
+
+ if (ajaxify.handleRedirects(url)) {
+ return true;
+ }
+
+ app.enterRoom('');
+
+ $(window).off('scroll');
+
+ if ($('#content').hasClass('ajaxifying') && apiXHR) {
+ apiXHR.abort();
+ }
+
+ url = ajaxify.start(url, quiet, search);
+
+ $('#footer, #content').removeClass('hide').addClass('ajaxifying');
+
+ ajaxify.variables.flush();
+ ajaxify.loadData(url, function(err, data) {
+ if (err) {
+ return onAjaxError(err, url, callback, quiet);
+ }
+
+ app.template = data.template.name;
+
+ require(['translator'], function(translator) {
+ translator.load(config.defaultLang, data.template.name);
+ renderTemplate(url, data.template.name, data, callback);
+ });
+ });
+
+ return true;
+ };
+
+ ajaxify.handleRedirects = function(url) {
+ url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase();
+ var isAdminRoute = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0;
+ var uploadsOrApi = url.startsWith('uploads') || url.startsWith('api');
+ if (isAdminRoute || uploadsOrApi) {
+ window.open(RELATIVE_PATH + '/' + url, '_top');
+ return true;
+ }
+ return false;
+ };
+
+
+ ajaxify.start = function(url, quiet, search) {
+ url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, ''));
+ var hash = window.location.hash;
+ search = search || '';
+
+ $(window).trigger('action:ajaxify.start', {url: url});
+
+ if (!window.location.pathname.match(/\/(403|404)$/g)) {
+ app.previousUrl = window.location.href;
+ }
+
+ ajaxify.currentPage = url;
+
+ if (window.history && window.history.pushState) {
+ window.history[!quiet ? 'pushState' : 'replaceState']({
+ url: url + search + hash
+ }, url, RELATIVE_PATH + '/' + url + search + hash);
+ }
+ return url;
+ };
+
+ function onAjaxError(err, url, callback, quiet) {
+ var data = err.data,
+ textStatus = err.textStatus;
+
+ if (data) {
+ var status = parseInt(data.status, 10);
+ if (status === 403 || status === 404 || status === 500 || status === 502) {
+ if (status === 502) {
+ status = 500;
+ }
+
+ $('#footer, #content').removeClass('hide').addClass('ajaxifying');
+ return renderTemplate(url, status.toString(), data.responseJSON, (new Date()).getTime(), callback);
+ } else if (status === 401) {
+ app.alertError('[[global:please_log_in]]');
+ app.previousUrl = url;
+ return ajaxify.go('login');
+ } else if (status === 302) {
+ if (data.responseJSON.external) {
+ window.location.href = data.responseJSON.external;
+ } else if (typeof data.responseJSON === 'string') {
+ ajaxify.go(data.responseJSON.slice(1), callback, quiet);
+ }
+ }
+ } else if (textStatus !== 'abort') {
+ app.alertError(data.responseJSON.error);
+ }
+ }
+
+ function renderTemplate(url, tpl_url, data, callback) {
+ $(window).trigger('action:ajaxify.loadingTemplates', {});
+
+ templates.parse(tpl_url, data, function(template) {
+ translator.translate(template, function(translatedTemplate) {
+ $('#content').html(translatedTemplate);
+
+ ajaxify.end(url, tpl_url);
+
+ if (typeof callback === 'function') {
+ callback();
+ }
+
+ $('#content, #footer').removeClass('ajaxifying');
+
+ app.refreshTitle(url);
+ });
+ });
+ }
+
+ ajaxify.end = function(url, tpl_url) {
+ function done() {
+ if (--count === 0) {
+ $(window).trigger('action:ajaxify.end', {url: url});
+ }
+ }
+ var count = 2;
+
+ ajaxify.variables.parse();
+
+ ajaxify.loadScript(tpl_url, done);
+
+ ajaxify.widgets.render(tpl_url, url, done);
+
+ $(window).trigger('action:ajaxify.contentLoaded', {url: url, tpl: tpl_url});
+
+ app.processPage();
+ };
+
+ ajaxify.removeRelativePath = function(url) {
+ if (url.startsWith(RELATIVE_PATH.slice(1))) {
+ url = url.slice(RELATIVE_PATH.length);
+ }
+ return url;
+ };
+
+ ajaxify.refresh = function(e) {
+ if (e && e instanceof jQuery.Event) {
+ e.preventDefault();
+ }
+
+ ajaxify.go(ajaxify.currentPage, null, true);
+ };
+
+ ajaxify.loadScript = function(tpl_url, callback) {
+ var location = !app.inAdmin ? 'forum/' : '';
+
+ require([location + tpl_url], function(script) {
+ if (script && script.init) {
+ script.init();
+ }
+
+ if (callback) {
+ callback();
+ }
+ });
+ };
+
+ ajaxify.loadData = function(url, callback) {
+ url = ajaxify.removeRelativePath(url);
+
+ $(window).trigger('action:ajaxify.loadingData', {url: url});
+
+ apiXHR = $.ajax({
+ url: RELATIVE_PATH + '/api/' + url,
+ cache: false,
+ success: function(data) {
+ if (!data) {
+ return;
+ }
+ ajaxify.data = data;
+ data.relative_path = RELATIVE_PATH;
+ $(window).trigger('action:ajaxify.dataLoaded', {url: url, data: data});
+
+ if (callback) {
+ callback(null, data);
+ }
+ },
+ error: function(data, textStatus) {
+ if (data.status === 0 && textStatus === 'error') {
+ data.status = 500;
+ }
+ callback({
+ data: data,
+ textStatus: textStatus
+ });
+ }
+ });
+ };
+
+ ajaxify.loadTemplate = function(template, callback) {
+ if (templates.cache[template]) {
+ callback(templates.cache[template]);
+ } else {
+ $.ajax({
+ url: RELATIVE_PATH + '/templates/' + template + '.tpl' + (config['cache-buster'] ? '?v=' + config['cache-buster'] : ''),
+ type: 'GET',
+ success: function(data) {
+ callback(data.toString());
+ },
+ error: function(error) {
+ throw new Error("Unable to load template: " + template + " (" + error.statusText + ")");
+ }
+ });
+ }
+ };
+
+ function ajaxifyAnchors() {
+ templates.registerLoader(ajaxify.loadTemplate);
+
+ function hrefEmpty(href) {
+ return href === undefined || href === '' || href === 'javascript:;';
+ }
+
+ // Enhancing all anchors to ajaxify...
+ $(document.body).on('click', 'a', function (e) {
+ if (this.target !== '' || (this.protocol !== 'http:' && this.protocol !== 'https:')) {
+ return;
+ } else if (hrefEmpty(this.href) || this.protocol === 'javascript:' || $(this).attr('data-ajaxify') === 'false' || $(this).attr('href') === '#') {
+ return e.preventDefault();
+ }
+
+ if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) {
+ if (
+ this.host === '' || // Relative paths are always internal links...
+ (this.host === window.location.host && this.protocol === window.location.protocol && // Otherwise need to check that protocol and host match
+ (RELATIVE_PATH.length > 0 ? this.pathname.indexOf(RELATIVE_PATH) === 0 : true)) // Subfolder installs need this additional check
+ ) {
+ // Internal link
+ var url = this.pathname.replace(RELATIVE_PATH + '/', '');
+
+ // Special handling for urls with hashes
+ if (window.location.pathname === this.pathname && this.hash.length) {
+ window.location.hash = this.hash;
+ } else {
+ window.location.hash = '';
+ if (ajaxify.go(url)) {
+ e.preventDefault();
+ }
+ }
+ } else if (window.location.pathname !== '/outgoing') {
+ // External Link
+ if (config.openOutgoingLinksInNewTab) {
+ window.open(this.href, '_blank');
+ e.preventDefault();
+ } else if (config.useOutgoingLinksPage) {
+ ajaxify.go('outgoing?url=' + encodeURIComponent(this.href));
+ e.preventDefault();
+ }
+ }
+ }
+ });
+ }
+
+ if (window.history && window.history.pushState) {
+ // Progressive Enhancement, ajaxify available only to modern browsers
+ ajaxifyAnchors();
+ }
+
+ app.load();
+ templates.cache['500'] = $('.tpl-500').html();
+
});
\ No newline at end of file
diff --git a/public/src/app.js b/public/src/app.js
index 3cf6947073..3254c7a51f 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -1,613 +1,613 @@
-"use strict";
-/*global io, templates, ajaxify, utils, bootbox, RELATIVE_PATH, config, Visibility*/
-
-var socket,
- app = app || {};
-
-app.isFocused = true;
-app.isConnected = false;
-app.currentRoom = null;
-app.widgets = {};
-app.cacheBuster = null;
-
-(function () {
- var showWelcomeMessage = false;
- var reconnecting = false;
-
- function socketIOConnect() {
- var ioParams = {
- reconnectionAttempts: config.maxReconnectionAttempts,
- reconnectionDelay: config.reconnectionDelay,
- transports: config.socketioTransports,
- path: config.relative_path + '/socket.io'
- };
-
- socket = io(config.websocketAddress, ioParams);
- reconnecting = false;
-
- socket.on('event:connect', function () {
- app.showLoginMessage();
- app.replaceSelfLinks();
- $(window).trigger('action:connected');
- app.isConnected = true;
- });
-
- socket.on('connect', onSocketConnect);
-
- socket.on('event:disconnect', function() {
- $(window).trigger('action:disconnected');
- app.isConnected = false;
- socket.connect();
- });
-
- socket.on('reconnecting', function (attempt) {
- reconnecting = true;
- var reconnectEl = $('#reconnect');
-
- if (!reconnectEl.hasClass('active')) {
- reconnectEl.html('');
- }
-
- reconnectEl.addClass('active').removeClass("hide").tooltip({
- placement: 'bottom'
- });
- });
-
- socket.on('event:banned', function() {
- app.alert({
- title: '[[global:alert.banned]]',
- message: '[[global:alert.banned.message]]',
- type: 'danger',
- timeout: 1000
- });
-
- setTimeout(function() {
- window.location.href = config.relative_path + '/';
- }, 1000);
- });
-
- socket.on('event:logout', app.logout);
-
- socket.on('event:alert', function(data) {
- app.alert(data);
- });
-
- socket.on('reconnect_failed', function() {
- // Wait ten times the reconnection delay and then start over
- setTimeout(socket.connect.bind(socket), parseInt(config.reconnectionDelay, 10) * 10);
- });
- }
-
- function onSocketConnect(data) {
- if (reconnecting) {
- var reconnectEl = $('#reconnect');
-
- reconnectEl.tooltip('destroy');
- reconnectEl.html('');
- reconnecting = false;
-
- // Rejoin room that was left when we disconnected
- var url_parts = window.location.pathname.slice(RELATIVE_PATH.length).split('/').slice(1);
- var room;
-
- switch(url_parts[0]) {
- case 'user':
- room = 'user/' + ajaxify.data.theirid;
- break;
- case 'topic':
- room = 'topic_' + url_parts[1];
- break;
- case 'category':
- room = 'category_' + url_parts[1];
- break;
- case 'recent':
- room = 'recent_topics';
- break;
- case 'unread':
- room = 'unread_topics';
- break;
- case 'popular':
- room = 'popular_topics';
- break;
- case 'admin':
- room = 'admin';
- break;
- case 'categories':
- room = 'categories';
- break;
- }
- app.currentRoom = '';
- app.enterRoom(room);
-
- socket.emit('meta.reconnected');
-
- app.isConnected = true;
- $(window).trigger('action:reconnected');
-
- setTimeout(function() {
- reconnectEl.removeClass('active').addClass('hide');
- }, 3000);
- }
- }
-
- app.logout = function() {
- require(['csrf'], function(csrf) {
- $.ajax(RELATIVE_PATH + '/logout', {
- type: 'POST',
- headers: {
- 'x-csrf-token': csrf.get()
- },
- success: function() {
- window.location.href = RELATIVE_PATH + '/';
- }
- });
- });
- };
-
- app.alert = function (params) {
- require(['alerts'], function(alerts) {
- alerts.alert(params);
- });
- };
-
- app.removeAlert = function(id) {
- require(['alerts'], function(alerts) {
- alerts.remove(id);
- });
- };
-
- app.alertSuccess = function (message, timeout) {
- app.alert({
- title: '[[global:alert.success]]',
- message: message,
- type: 'success',
- timeout: timeout ? timeout : 2000
- });
- };
-
- app.alertError = function (message, timeout) {
- app.alert({
- title: '[[global:alert.error]]',
- message: message,
- type: 'danger',
- timeout: timeout ? timeout : 5000
- });
- };
-
- app.enterRoom = function (room, callback) {
- callback = callback || function() {};
- if (socket) {
- if (app.currentRoom === room) {
- return;
- }
-
- socket.emit('meta.rooms.enter', {
- enter: room,
- username: app.user.username,
- userslug: app.user.userslug,
- picture: app.user.picture
- }, function(err) {
- if (err) {
- app.alertError(err.message);
- return;
- }
- app.currentRoom = room;
- });
- }
- };
-
- function highlightNavigationLink() {
- var path = window.location.pathname;
- $('#main-nav li').removeClass('active');
- if (path) {
- $('#main-nav li a').each(function () {
- var href = $(this).attr('href');
-
- if (href && path.startsWith(href)) {
- $(this.parentNode).addClass('active');
- return false;
- }
- });
- }
- }
-
- app.createUserTooltips = function() {
- $('img[title].teaser-pic,img[title].user-img').each(function() {
- $(this).tooltip({
- placement: 'top',
- title: $(this).attr('title')
- });
- });
- };
-
- app.createStatusTooltips = function() {
- $('body').tooltip({
- selector:'.fa-circle.status',
- placement: 'top'
- });
- };
-
- app.replaceSelfLinks = function(selector) {
- selector = selector || $('a');
- selector.each(function() {
- var href = $(this).attr('href');
- if (href && app.user.userslug && href.indexOf('user/_self_') !== -1) {
- $(this).attr('href', href.replace(/user\/_self_/g, 'user/' + app.user.userslug));
- }
- });
- };
-
- app.processPage = function () {
- highlightNavigationLink();
-
- $('.timeago').timeago();
-
- utils.makeNumbersHumanReadable($('.human-readable-number'));
-
- utils.addCommasToNumbers($('.formatted-number'));
-
- app.createUserTooltips();
-
- app.createStatusTooltips();
-
- app.replaceSelfLinks();
-
- // Scroll back to top of page
- window.scrollTo(0, 0);
- };
-
- app.showLoginMessage = function () {
- function showAlert() {
- app.alert({
- type: 'success',
- title: '[[global:welcome_back]] ' + app.user.username + '!',
- message: '[[global:you_have_successfully_logged_in]]',
- timeout: 5000
- });
- }
-
- if (showWelcomeMessage) {
- showWelcomeMessage = false;
- if (document.readyState !== 'complete') {
- $(document).ready(showAlert);
- } else {
- showAlert();
- }
- }
- };
-
- app.openChat = function (username, touid) {
- if (username === app.user.username) {
- return app.alertError('[[error:cant-chat-with-yourself]]');
- }
-
- if (!app.user.uid) {
- return app.alertError('[[error:not-logged-in]]');
- }
-
- require(['chat'], function (chat) {
- function loadAndCenter(chatModal) {
- chat.load(chatModal.attr('UUID'));
- chat.center(chatModal);
- chat.focusInput(chatModal);
- }
-
- if (!chat.modalExists(touid)) {
- chat.createModal(username, touid, loadAndCenter);
- } else {
- loadAndCenter(chat.getModal(touid));
- }
- });
- };
-
- var titleObj = {
- active: false,
- interval: undefined,
- titles: []
- };
-
- app.alternatingTitle = function (title) {
- if (typeof title !== 'string') {
- return;
- }
-
- if (title.length > 0 && !app.isFocused) {
- if (!titleObj.titles[0]) {
- titleObj.titles[0] = window.document.title;
- }
-
- require(['translator'], function(translator) {
- translator.translate(title, function(translated) {
- titleObj.titles[1] = translated;
- if (titleObj.interval) {
- clearInterval(titleObj.interval);
- }
-
- titleObj.interval = setInterval(function() {
- var title = titleObj.titles[titleObj.titles.indexOf(window.document.title) ^ 1];
- if (title) {
- window.document.title = $('').html(title).text();
- }
- }, 2000);
- });
- });
- } else {
- if (titleObj.interval) {
- clearInterval(titleObj.interval);
- }
- if (titleObj.titles[0]) {
- window.document.title = $('').html(titleObj.titles[0]).text();
- }
- }
- };
-
- app.refreshTitle = function(url) {
- if (!url) {
- var a = document.createElement('a');
- a.href = document.location;
- url = a.pathname.slice(1);
- }
-
- socket.emit('meta.buildTitle', url, function(err, title, numNotifications) {
- if (err) {
- return;
- }
- titleObj.titles[0] = (numNotifications > 0 ? '(' + numNotifications + ') ' : '') + title;
- app.alternatingTitle('');
- });
- };
-
- app.toggleNavbar = function(state) {
- var navbarEl = $('.navbar');
- if (navbarEl) {
- navbarEl.toggleClass('hidden', !!!state);
- }
- };
-
- app.exposeConfigToTemplates = function() {
- $(document).ready(function() {
- templates.setGlobal('loggedIn', config.loggedIn);
- templates.setGlobal('relative_path', RELATIVE_PATH);
- for(var key in config) {
- if (config.hasOwnProperty(key)) {
- templates.setGlobal('config.' + key, config[key]);
- }
- }
- });
- };
-
- function createHeaderTooltips() {
- if (utils.findBootstrapEnvironment() === 'xs') {
- return;
- }
- $('#header-menu li a[title]').each(function() {
- $(this).tooltip({
- placement: 'bottom',
- title: $(this).attr('title')
- });
- });
-
- $('#search-form').parent().tooltip({
- placement: 'bottom',
- title: $('#search-button i').attr('title')
- });
-
- $('#user_dropdown').tooltip({
- placement: 'bottom',
- title: $('#user_dropdown').attr('title')
- });
- }
-
- app.handleSearch = function () {
- var searchButton = $("#search-button"),
- searchFields = $("#search-fields"),
- searchInput = $('#search-fields input');
-
- $('#search-form').on('submit', dismissSearch);
- searchInput.on('blur', dismissSearch);
-
- function dismissSearch(){
- searchFields.hide();
- searchButton.show();
- }
-
- searchButton.on('click', function(e) {
- if (!config.loggedIn && !config.allowGuestSearching) {
- app.alert({
- message:'[[error:search-requires-login]]',
- timeout: 3000
- });
- ajaxify.go('login');
- return false;
- }
- e.stopPropagation();
-
- app.prepareSearch();
- return false;
- });
-
- $('#search-form').on('submit', function () {
- var input = $(this).find('input');
- require(['search'], function(search) {
- search.query({term: input.val()}, function() {
- input.val('');
- });
- });
- return false;
- });
- };
-
- app.prepareSearch = function() {
- $("#search-fields").removeClass('hide').show();
- $("#search-button").hide();
- $('#search-fields input').focus();
- };
-
- function handleStatusChange() {
- $('#user-control-list .user-status').off('click').on('click', function(e) {
- var status = $(this).attr('data-status');
- socket.emit('user.setStatus', status, function(err, data) {
- if(err) {
- return app.alertError(err.message);
- }
- $('#logged-in-menu #user_label #user-profile-link>i').attr('class', 'fa fa-circle status ' + status);
- });
- e.preventDefault();
- });
- }
-
- app.updateUserStatus = function(el, status) {
- if (!el.length) {
- return;
- }
-
- translator.translate('[[global:' + status + ']]', function(translated) {
- el.removeClass('online offline dnd away')
- .addClass(status)
- .attr('title', translated)
- .attr('data-original-title', translated);
- });
- };
-
- function handleNewTopic() {
- $('#content').on('click', '#new_topic', function() {
- var cid = ajaxify.data.cid;
- if (cid) {
- $(window).trigger('action:composer.topic.new', {
- cid: cid
- });
- } else {
- socket.emit('categories.getCategoriesByPrivilege', 'topics:create', function(err, categories) {
- if (err) {
- return app.alertError(err.message);
- }
- if (categories.length) {
- $(window).trigger('action:composer.topic.new', {
- cid: categories[0].cid
- });
- }
- });
- }
- });
- }
-
- app.load = function() {
- $('document').ready(function () {
- var url = ajaxify.start(window.location.pathname.slice(1), true, window.location.search);
- ajaxify.end(url, app.template);
-
- handleStatusChange();
-
- if (config.searchEnabled) {
- app.handleSearch();
- }
-
- handleNewTopic();
-
- $('#logout-link').on('click', app.logout);
-
- Visibility.change(function(e, state){
- if (state === 'visible') {
- app.isFocused = true;
- app.alternatingTitle('');
- } else if (state === 'hidden') {
- app.isFocused = false;
- }
- });
-
- createHeaderTooltips();
- app.showEmailConfirmWarning();
-
- socket.removeAllListeners('event:nodebb.ready');
- socket.on('event:nodebb.ready', function(cacheBusters) {
- if (
- !app.cacheBusters ||
- app.cacheBusters.general !== cacheBusters.general ||
- app.cacheBusters.css !== cacheBusters.css ||
- app.cacheBusters.js !== cacheBusters.js
- ) {
- app.cacheBusters = cacheBusters;
-
- app.alert({
- alert_id: 'forum_updated',
- title: '[[global:updated.title]]',
- message: '[[global:updated.message]]',
- clickfn: function() {
- window.location.reload();
- },
- type: 'warning'
- });
- }
- });
-
- require(['taskbar', 'helpers'], function(taskbar, helpers) {
- taskbar.init();
-
- // templates.js helpers
- helpers.register();
-
- $(window).trigger('action:app.load');
- });
- });
- };
-
- app.loadJQueryUI = function(callback) {
- if (typeof $().autocomplete === 'function') {
- return callback();
- }
-
- $.getScript(RELATIVE_PATH + '/vendor/jquery/js/jquery-ui-1.10.4.custom.js', callback);
- };
-
- app.showEmailConfirmWarning = function(err) {
- if (!config.requireEmailConfirmation || !app.user.uid) {
- return;
- }
- if (!app.user.email) {
- app.alert({
- alert_id: 'email_confirm',
- message: '[[error:no-email-to-confirm]]',
- type: 'warning',
- timeout: 0,
- clickfn: function() {
- app.removeAlert('email_confirm');
- ajaxify.go('user/' + app.user.userslug + '/edit');
- }
- });
- } else if (!app.user['email:confirmed']) {
- app.alert({
- alert_id: 'email_confirm',
- message: err ? err.message : '[[error:email-not-confirmed]]',
- type: 'warning',
- timeout: 0,
- clickfn: function() {
- app.removeAlert('email_confirm');
- socket.emit('user.emailConfirm', {}, function(err) {
- if (err) {
- return app.alertError(err.message);
- }
- app.alertSuccess('[[notifications:email-confirm-sent]]');
- });
- }
- });
- }
- };
-
- showWelcomeMessage = window.location.href.indexOf('loggedin') !== -1;
-
- app.exposeConfigToTemplates();
-
- socketIOConnect();
-
- app.cacheBuster = config['cache-buster'];
-
- require(['csrf'], function(csrf) {
- csrf.set(config.csrf_token);
- });
-
- bootbox.setDefaults({
- locale: config.userLang
- });
-
- app.alternatingTitle('');
-}());
+"use strict";
+/*global io, templates, ajaxify, utils, bootbox, RELATIVE_PATH, config, Visibility*/
+
+var socket,
+ app = app || {};
+
+app.isFocused = true;
+app.isConnected = false;
+app.currentRoom = null;
+app.widgets = {};
+app.cacheBuster = null;
+
+(function () {
+ var showWelcomeMessage = false;
+ var reconnecting = false;
+
+ function socketIOConnect() {
+ var ioParams = {
+ reconnectionAttempts: config.maxReconnectionAttempts,
+ reconnectionDelay: config.reconnectionDelay,
+ transports: config.socketioTransports,
+ path: config.relative_path + '/socket.io'
+ };
+
+ socket = io(config.websocketAddress, ioParams);
+ reconnecting = false;
+
+ socket.on('event:connect', function () {
+ app.showLoginMessage();
+ app.replaceSelfLinks();
+ $(window).trigger('action:connected');
+ app.isConnected = true;
+ });
+
+ socket.on('connect', onSocketConnect);
+
+ socket.on('event:disconnect', function() {
+ $(window).trigger('action:disconnected');
+ app.isConnected = false;
+ socket.connect();
+ });
+
+ socket.on('reconnecting', function (attempt) {
+ reconnecting = true;
+ var reconnectEl = $('#reconnect');
+
+ if (!reconnectEl.hasClass('active')) {
+ reconnectEl.html('');
+ }
+
+ reconnectEl.addClass('active').removeClass("hide").tooltip({
+ placement: 'bottom'
+ });
+ });
+
+ socket.on('event:banned', function() {
+ app.alert({
+ title: '[[global:alert.banned]]',
+ message: '[[global:alert.banned.message]]',
+ type: 'danger',
+ timeout: 1000
+ });
+
+ setTimeout(function() {
+ window.location.href = config.relative_path + '/';
+ }, 1000);
+ });
+
+ socket.on('event:logout', app.logout);
+
+ socket.on('event:alert', function(data) {
+ app.alert(data);
+ });
+
+ socket.on('reconnect_failed', function() {
+ // Wait ten times the reconnection delay and then start over
+ setTimeout(socket.connect.bind(socket), parseInt(config.reconnectionDelay, 10) * 10);
+ });
+ }
+
+ function onSocketConnect(data) {
+ if (reconnecting) {
+ var reconnectEl = $('#reconnect');
+
+ reconnectEl.tooltip('destroy');
+ reconnectEl.html('');
+ reconnecting = false;
+
+ // Rejoin room that was left when we disconnected
+ var url_parts = window.location.pathname.slice(RELATIVE_PATH.length).split('/').slice(1);
+ var room;
+
+ switch(url_parts[0]) {
+ case 'user':
+ room = 'user/' + ajaxify.data.theirid;
+ break;
+ case 'topic':
+ room = 'topic_' + url_parts[1];
+ break;
+ case 'category':
+ room = 'category_' + url_parts[1];
+ break;
+ case 'recent':
+ room = 'recent_topics';
+ break;
+ case 'unread':
+ room = 'unread_topics';
+ break;
+ case 'popular':
+ room = 'popular_topics';
+ break;
+ case 'admin':
+ room = 'admin';
+ break;
+ case 'categories':
+ room = 'categories';
+ break;
+ }
+ app.currentRoom = '';
+ app.enterRoom(room);
+
+ socket.emit('meta.reconnected');
+
+ app.isConnected = true;
+ $(window).trigger('action:reconnected');
+
+ setTimeout(function() {
+ reconnectEl.removeClass('active').addClass('hide');
+ }, 3000);
+ }
+ }
+
+ app.logout = function() {
+ require(['csrf'], function(csrf) {
+ $.ajax(RELATIVE_PATH + '/logout', {
+ type: 'POST',
+ headers: {
+ 'x-csrf-token': csrf.get()
+ },
+ success: function() {
+ window.location.href = RELATIVE_PATH + '/';
+ }
+ });
+ });
+ };
+
+ app.alert = function (params) {
+ require(['alerts'], function(alerts) {
+ alerts.alert(params);
+ });
+ };
+
+ app.removeAlert = function(id) {
+ require(['alerts'], function(alerts) {
+ alerts.remove(id);
+ });
+ };
+
+ app.alertSuccess = function (message, timeout) {
+ app.alert({
+ title: '[[global:alert.success]]',
+ message: message,
+ type: 'success',
+ timeout: timeout ? timeout : 2000
+ });
+ };
+
+ app.alertError = function (message, timeout) {
+ app.alert({
+ title: '[[global:alert.error]]',
+ message: message,
+ type: 'danger',
+ timeout: timeout ? timeout : 5000
+ });
+ };
+
+ app.enterRoom = function (room, callback) {
+ callback = callback || function() {};
+ if (socket) {
+ if (app.currentRoom === room) {
+ return;
+ }
+
+ socket.emit('meta.rooms.enter', {
+ enter: room,
+ username: app.user.username,
+ userslug: app.user.userslug,
+ picture: app.user.picture
+ }, function(err) {
+ if (err) {
+ app.alertError(err.message);
+ return;
+ }
+ app.currentRoom = room;
+ });
+ }
+ };
+
+ function highlightNavigationLink() {
+ var path = window.location.pathname;
+ $('#main-nav li').removeClass('active');
+ if (path) {
+ $('#main-nav li a').each(function () {
+ var href = $(this).attr('href');
+
+ if (href && path.startsWith(href)) {
+ $(this.parentNode).addClass('active');
+ return false;
+ }
+ });
+ }
+ }
+
+ app.createUserTooltips = function() {
+ $('img[title].teaser-pic,img[title].user-img').each(function() {
+ $(this).tooltip({
+ placement: 'top',
+ title: $(this).attr('title')
+ });
+ });
+ };
+
+ app.createStatusTooltips = function() {
+ $('body').tooltip({
+ selector:'.fa-circle.status',
+ placement: 'top'
+ });
+ };
+
+ app.replaceSelfLinks = function(selector) {
+ selector = selector || $('a');
+ selector.each(function() {
+ var href = $(this).attr('href');
+ if (href && app.user.userslug && href.indexOf('user/_self_') !== -1) {
+ $(this).attr('href', href.replace(/user\/_self_/g, 'user/' + app.user.userslug));
+ }
+ });
+ };
+
+ app.processPage = function () {
+ highlightNavigationLink();
+
+ $('.timeago').timeago();
+
+ utils.makeNumbersHumanReadable($('.human-readable-number'));
+
+ utils.addCommasToNumbers($('.formatted-number'));
+
+ app.createUserTooltips();
+
+ app.createStatusTooltips();
+
+ app.replaceSelfLinks();
+
+ // Scroll back to top of page
+ window.scrollTo(0, 0);
+ };
+
+ app.showLoginMessage = function () {
+ function showAlert() {
+ app.alert({
+ type: 'success',
+ title: '[[global:welcome_back]] ' + app.user.username + '!',
+ message: '[[global:you_have_successfully_logged_in]]',
+ timeout: 5000
+ });
+ }
+
+ if (showWelcomeMessage) {
+ showWelcomeMessage = false;
+ if (document.readyState !== 'complete') {
+ $(document).ready(showAlert);
+ } else {
+ showAlert();
+ }
+ }
+ };
+
+ app.openChat = function (username, touid) {
+ if (username === app.user.username) {
+ return app.alertError('[[error:cant-chat-with-yourself]]');
+ }
+
+ if (!app.user.uid) {
+ return app.alertError('[[error:not-logged-in]]');
+ }
+
+ require(['chat'], function (chat) {
+ function loadAndCenter(chatModal) {
+ chat.load(chatModal.attr('UUID'));
+ chat.center(chatModal);
+ chat.focusInput(chatModal);
+ }
+
+ if (!chat.modalExists(touid)) {
+ chat.createModal(username, touid, loadAndCenter);
+ } else {
+ loadAndCenter(chat.getModal(touid));
+ }
+ });
+ };
+
+ var titleObj = {
+ active: false,
+ interval: undefined,
+ titles: []
+ };
+
+ app.alternatingTitle = function (title) {
+ if (typeof title !== 'string') {
+ return;
+ }
+
+ if (title.length > 0 && !app.isFocused) {
+ if (!titleObj.titles[0]) {
+ titleObj.titles[0] = window.document.title;
+ }
+
+ require(['translator'], function(translator) {
+ translator.translate(title, function(translated) {
+ titleObj.titles[1] = translated;
+ if (titleObj.interval) {
+ clearInterval(titleObj.interval);
+ }
+
+ titleObj.interval = setInterval(function() {
+ var title = titleObj.titles[titleObj.titles.indexOf(window.document.title) ^ 1];
+ if (title) {
+ window.document.title = $('').html(title).text();
+ }
+ }, 2000);
+ });
+ });
+ } else {
+ if (titleObj.interval) {
+ clearInterval(titleObj.interval);
+ }
+ if (titleObj.titles[0]) {
+ window.document.title = $('').html(titleObj.titles[0]).text();
+ }
+ }
+ };
+
+ app.refreshTitle = function(url) {
+ if (!url) {
+ var a = document.createElement('a');
+ a.href = document.location;
+ url = a.pathname.slice(1);
+ }
+
+ socket.emit('meta.buildTitle', url, function(err, title, numNotifications) {
+ if (err) {
+ return;
+ }
+ titleObj.titles[0] = (numNotifications > 0 ? '(' + numNotifications + ') ' : '') + title;
+ app.alternatingTitle('');
+ });
+ };
+
+ app.toggleNavbar = function(state) {
+ var navbarEl = $('.navbar');
+ if (navbarEl) {
+ navbarEl.toggleClass('hidden', !!!state);
+ }
+ };
+
+ app.exposeConfigToTemplates = function() {
+ $(document).ready(function() {
+ templates.setGlobal('loggedIn', config.loggedIn);
+ templates.setGlobal('relative_path', RELATIVE_PATH);
+ for(var key in config) {
+ if (config.hasOwnProperty(key)) {
+ templates.setGlobal('config.' + key, config[key]);
+ }
+ }
+ });
+ };
+
+ function createHeaderTooltips() {
+ if (utils.findBootstrapEnvironment() === 'xs') {
+ return;
+ }
+ $('#header-menu li a[title]').each(function() {
+ $(this).tooltip({
+ placement: 'bottom',
+ title: $(this).attr('title')
+ });
+ });
+
+ $('#search-form').parent().tooltip({
+ placement: 'bottom',
+ title: $('#search-button i').attr('title')
+ });
+
+ $('#user_dropdown').tooltip({
+ placement: 'bottom',
+ title: $('#user_dropdown').attr('title')
+ });
+ }
+
+ app.handleSearch = function () {
+ var searchButton = $("#search-button"),
+ searchFields = $("#search-fields"),
+ searchInput = $('#search-fields input');
+
+ $('#search-form').on('submit', dismissSearch);
+ searchInput.on('blur', dismissSearch);
+
+ function dismissSearch(){
+ searchFields.hide();
+ searchButton.show();
+ }
+
+ searchButton.on('click', function(e) {
+ if (!config.loggedIn && !config.allowGuestSearching) {
+ app.alert({
+ message:'[[error:search-requires-login]]',
+ timeout: 3000
+ });
+ ajaxify.go('login');
+ return false;
+ }
+ e.stopPropagation();
+
+ app.prepareSearch();
+ return false;
+ });
+
+ $('#search-form').on('submit', function () {
+ var input = $(this).find('input');
+ require(['search'], function(search) {
+ search.query({term: input.val()}, function() {
+ input.val('');
+ });
+ });
+ return false;
+ });
+ };
+
+ app.prepareSearch = function() {
+ $("#search-fields").removeClass('hide').show();
+ $("#search-button").hide();
+ $('#search-fields input').focus();
+ };
+
+ function handleStatusChange() {
+ $('#user-control-list .user-status').off('click').on('click', function(e) {
+ var status = $(this).attr('data-status');
+ socket.emit('user.setStatus', status, function(err, data) {
+ if(err) {
+ return app.alertError(err.message);
+ }
+ $('#logged-in-menu #user_label #user-profile-link>i').attr('class', 'fa fa-circle status ' + status);
+ });
+ e.preventDefault();
+ });
+ }
+
+ app.updateUserStatus = function(el, status) {
+ if (!el.length) {
+ return;
+ }
+
+ translator.translate('[[global:' + status + ']]', function(translated) {
+ el.removeClass('online offline dnd away')
+ .addClass(status)
+ .attr('title', translated)
+ .attr('data-original-title', translated);
+ });
+ };
+
+ function handleNewTopic() {
+ $('#content').on('click', '#new_topic', function() {
+ var cid = ajaxify.data.cid;
+ if (cid) {
+ $(window).trigger('action:composer.topic.new', {
+ cid: cid
+ });
+ } else {
+ socket.emit('categories.getCategoriesByPrivilege', 'topics:create', function(err, categories) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ if (categories.length) {
+ $(window).trigger('action:composer.topic.new', {
+ cid: categories[0].cid
+ });
+ }
+ });
+ }
+ });
+ }
+
+ app.load = function() {
+ $('document').ready(function () {
+ var url = ajaxify.start(window.location.pathname.slice(1), true, window.location.search);
+ ajaxify.end(url, app.template);
+
+ handleStatusChange();
+
+ if (config.searchEnabled) {
+ app.handleSearch();
+ }
+
+ handleNewTopic();
+
+ $('#logout-link').on('click', app.logout);
+
+ Visibility.change(function(e, state){
+ if (state === 'visible') {
+ app.isFocused = true;
+ app.alternatingTitle('');
+ } else if (state === 'hidden') {
+ app.isFocused = false;
+ }
+ });
+
+ createHeaderTooltips();
+ app.showEmailConfirmWarning();
+
+ socket.removeAllListeners('event:nodebb.ready');
+ socket.on('event:nodebb.ready', function(cacheBusters) {
+ if (
+ !app.cacheBusters ||
+ app.cacheBusters.general !== cacheBusters.general ||
+ app.cacheBusters.css !== cacheBusters.css ||
+ app.cacheBusters.js !== cacheBusters.js
+ ) {
+ app.cacheBusters = cacheBusters;
+
+ app.alert({
+ alert_id: 'forum_updated',
+ title: '[[global:updated.title]]',
+ message: '[[global:updated.message]]',
+ clickfn: function() {
+ window.location.reload();
+ },
+ type: 'warning'
+ });
+ }
+ });
+
+ require(['taskbar', 'helpers'], function(taskbar, helpers) {
+ taskbar.init();
+
+ // templates.js helpers
+ helpers.register();
+
+ $(window).trigger('action:app.load');
+ });
+ });
+ };
+
+ app.loadJQueryUI = function(callback) {
+ if (typeof $().autocomplete === 'function') {
+ return callback();
+ }
+
+ $.getScript(RELATIVE_PATH + '/vendor/jquery/js/jquery-ui-1.10.4.custom.js', callback);
+ };
+
+ app.showEmailConfirmWarning = function(err) {
+ if (!config.requireEmailConfirmation || !app.user.uid) {
+ return;
+ }
+ if (!app.user.email) {
+ app.alert({
+ alert_id: 'email_confirm',
+ message: '[[error:no-email-to-confirm]]',
+ type: 'warning',
+ timeout: 0,
+ clickfn: function() {
+ app.removeAlert('email_confirm');
+ ajaxify.go('user/' + app.user.userslug + '/edit');
+ }
+ });
+ } else if (!app.user['email:confirmed']) {
+ app.alert({
+ alert_id: 'email_confirm',
+ message: err ? err.message : '[[error:email-not-confirmed]]',
+ type: 'warning',
+ timeout: 0,
+ clickfn: function() {
+ app.removeAlert('email_confirm');
+ socket.emit('user.emailConfirm', {}, function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[notifications:email-confirm-sent]]');
+ });
+ }
+ });
+ }
+ };
+
+ showWelcomeMessage = window.location.href.indexOf('loggedin') !== -1;
+
+ app.exposeConfigToTemplates();
+
+ socketIOConnect();
+
+ app.cacheBuster = config['cache-buster'];
+
+ require(['csrf'], function(csrf) {
+ csrf.set(config.csrf_token);
+ });
+
+ bootbox.setDefaults({
+ locale: config.userLang
+ });
+
+ app.alternatingTitle('');
+}());
diff --git a/public/vendor/fontawesome/.gitignore b/public/vendor/fontawesome/.gitignore
index 51d8053d8c..aaad45f3c8 100644
--- a/public/vendor/fontawesome/.gitignore
+++ b/public/vendor/fontawesome/.gitignore
@@ -1,29 +1,29 @@
-*.pyc
-*.egg-info
-*.db
-*.db.old
-*.swp
-*.db-journal
-
-.coverage
-.DS_Store
-.installed.cfg
-
-.idea/*
-.svn/*
-src/website/static/*
-src/website/media/*
-
-bin
-build
-cfcache
-develop-eggs
-dist
-downloads
-eggs
-parts
+*.pyc
+*.egg-info
+*.db
+*.db.old
+*.swp
+*.db-journal
+
+.coverage
+.DS_Store
+.installed.cfg
+
+.idea/*
+.svn/*
+src/website/static/*
+src/website/media/*
+
+bin
+build
+cfcache
+develop-eggs
+dist
+downloads
+eggs
+parts
tmp
-.sass-cache
-
-src/website/settingslocal.py
+.sass-cache
+
+src/website/settingslocal.py
stunnel.log
\ No newline at end of file
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js b/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js
index 0a70819d44..2bfbf53ccd 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js
@@ -1,22 +1,22 @@
-
-// Persian
-// Use DIR attribute for RTL text in Persian Language for ABBR tag .
-// By MB.seifollahi@gmail.com
-jQuery.timeago.settings.strings = {
- prefixAgo: null,
- prefixFromNow: null,
- suffixAgo: "پیش",
- suffixFromNow: "از حال",
- seconds: "کمتر از یک دقیقه",
- minute: "حدود یک دقیقه",
- minutes: "%d دقیقه",
- hour: "حدود یک ساعت",
- hours: "حدود %d ساعت",
- day: "یک روز",
- days: "%d روز",
- month: "حدود یک ماه",
- months: "%d ماه",
- year: "حدود یک سال",
- years: "%d سال",
- wordSeparator: " "
+
+// Persian
+// Use DIR attribute for RTL text in Persian Language for ABBR tag .
+// By MB.seifollahi@gmail.com
+jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "پیش",
+ suffixFromNow: "از حال",
+ seconds: "کمتر از یک دقیقه",
+ minute: "حدود یک دقیقه",
+ minutes: "%d دقیقه",
+ hour: "حدود یک ساعت",
+ hours: "حدود %d ساعت",
+ day: "یک روز",
+ days: "%d روز",
+ month: "حدود یک ماه",
+ months: "%d ماه",
+ year: "حدود یک سال",
+ years: "%d سال",
+ wordSeparator: " "
};
\ No newline at end of file
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js b/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js
index 31c0c02af5..380f18dc04 100755
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js
@@ -1,19 +1,19 @@
-//Uzbek
-jQuery.timeago.settings.strings = {
- prefixAgo: null,
- prefixFromNow: "keyin",
- suffixAgo: "avval",
- suffixFromNow: null,
- seconds: "bir necha soniya",
- minute: "1 daqiqa",
- minutes: function(value) { return "%d daqiqa" },
- hour: "1 soat",
- hours: function(value) { return "%d soat" },
- day: "1 kun",
- days: function(value) { return "%d kun" },
- month: "1 oy",
- months: function(value) { return "%d oy" },
- year: "1 yil",
- years: function(value) { return "%d yil" },
- wordSeparator: " "
-};
+//Uzbek
+jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: "keyin",
+ suffixAgo: "avval",
+ suffixFromNow: null,
+ seconds: "bir necha soniya",
+ minute: "1 daqiqa",
+ minutes: function(value) { return "%d daqiqa" },
+ hour: "1 soat",
+ hours: function(value) { return "%d soat" },
+ day: "1 kun",
+ days: function(value) { return "%d kun" },
+ month: "1 oy",
+ months: function(value) { return "%d oy" },
+ year: "1 yil",
+ years: function(value) { return "%d yil" },
+ wordSeparator: " "
+};
diff --git a/src/routes/admin.js b/src/routes/admin.js
index 40bb725e89..a8d95776ee 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -1,85 +1,85 @@
-"use strict";
-
-var express = require('express');
-
-
-function apiRoutes(router, middleware, controllers) {
- router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
-
- var multipart = require('connect-multiparty');
- var multipartMiddleware = multipart();
-
- var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.authenticate];
-
- router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
- router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
- router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
- router.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
-}
-
-function adminRouter(middleware, controllers) {
- var router = express.Router();
-
- router.use(middleware.admin.buildHeader);
-
- addRoutes(router, middleware, controllers);
-
- return router;
-}
-
-function apiRouter(middleware, controllers) {
- var router = express.Router();
-
- addRoutes(router, middleware, controllers);
-
- apiRoutes(router, middleware, controllers);
-
- return router;
-}
-
-function addRoutes(router, middleware, controllers) {
- router.get('/', controllers.admin.home);
- router.get('/general/dashboard', controllers.admin.home);
- router.get('/general/languages', controllers.admin.languages.get);
- router.get('/general/sounds', controllers.admin.sounds.get);
- router.get('/general/navigation', controllers.admin.navigation.get);
- router.get('/general/homepage', controllers.admin.homepage.get);
-
- router.get('/manage/categories', controllers.admin.categories.getAll);
- router.get('/manage/categories/:category_id', controllers.admin.categories.get);
-
- router.get('/manage/tags', controllers.admin.tags.get);
-
- router.get('/manage/flags', controllers.admin.flags.get);
-
- router.get('/manage/users', controllers.admin.users.sortByJoinDate);
- router.get('/manage/users/search', controllers.admin.users.search);
- router.get('/manage/users/latest', controllers.admin.users.sortByJoinDate);
- router.get('/manage/users/sort-posts', controllers.admin.users.sortByPosts);
- router.get('/manage/users/sort-reputation', controllers.admin.users.sortByReputation);
- router.get('/manage/users/banned', controllers.admin.users.banned);
- router.get('/manage/users/registration', controllers.admin.users.registrationQueue);
-
- router.get('/manage/groups', controllers.admin.groups.list);
- router.get('/manage/groups/:name', controllers.admin.groups.get);
-
- router.get('/settings/:term?', controllers.admin.settings.get);
-
- router.get('/appearance/:term?', controllers.admin.appearance.get);
-
- router.get('/extend/plugins', controllers.admin.plugins.get);
- router.get('/extend/widgets', controllers.admin.extend.widgets);
- router.get('/extend/rewards', controllers.admin.extend.rewards);
-
- router.get('/advanced/database', controllers.admin.database.get);
- router.get('/advanced/events', controllers.admin.events.get);
- router.get('/advanced/logs', controllers.admin.logs.get);
- router.get('/advanced/post-cache', controllers.admin.postCache.get);
-
- router.get('/development/logger', controllers.admin.logger.get);
-}
-
-module.exports = function(app, middleware, controllers) {
- app.use('/admin/', adminRouter(middleware, controllers));
- app.use('/api/admin/', apiRouter(middleware, controllers));
-};
+"use strict";
+
+var express = require('express');
+
+
+function apiRoutes(router, middleware, controllers) {
+ router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
+
+ var multipart = require('connect-multiparty');
+ var multipartMiddleware = multipart();
+
+ var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.authenticate];
+
+ router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
+ router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
+ router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
+ router.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
+}
+
+function adminRouter(middleware, controllers) {
+ var router = express.Router();
+
+ router.use(middleware.admin.buildHeader);
+
+ addRoutes(router, middleware, controllers);
+
+ return router;
+}
+
+function apiRouter(middleware, controllers) {
+ var router = express.Router();
+
+ addRoutes(router, middleware, controllers);
+
+ apiRoutes(router, middleware, controllers);
+
+ return router;
+}
+
+function addRoutes(router, middleware, controllers) {
+ router.get('/', controllers.admin.home);
+ router.get('/general/dashboard', controllers.admin.home);
+ router.get('/general/languages', controllers.admin.languages.get);
+ router.get('/general/sounds', controllers.admin.sounds.get);
+ router.get('/general/navigation', controllers.admin.navigation.get);
+ router.get('/general/homepage', controllers.admin.homepage.get);
+
+ router.get('/manage/categories', controllers.admin.categories.getAll);
+ router.get('/manage/categories/:category_id', controllers.admin.categories.get);
+
+ router.get('/manage/tags', controllers.admin.tags.get);
+
+ router.get('/manage/flags', controllers.admin.flags.get);
+
+ router.get('/manage/users', controllers.admin.users.sortByJoinDate);
+ router.get('/manage/users/search', controllers.admin.users.search);
+ router.get('/manage/users/latest', controllers.admin.users.sortByJoinDate);
+ router.get('/manage/users/sort-posts', controllers.admin.users.sortByPosts);
+ router.get('/manage/users/sort-reputation', controllers.admin.users.sortByReputation);
+ router.get('/manage/users/banned', controllers.admin.users.banned);
+ router.get('/manage/users/registration', controllers.admin.users.registrationQueue);
+
+ router.get('/manage/groups', controllers.admin.groups.list);
+ router.get('/manage/groups/:name', controllers.admin.groups.get);
+
+ router.get('/settings/:term?', controllers.admin.settings.get);
+
+ router.get('/appearance/:term?', controllers.admin.appearance.get);
+
+ router.get('/extend/plugins', controllers.admin.plugins.get);
+ router.get('/extend/widgets', controllers.admin.extend.widgets);
+ router.get('/extend/rewards', controllers.admin.extend.rewards);
+
+ router.get('/advanced/database', controllers.admin.database.get);
+ router.get('/advanced/events', controllers.admin.events.get);
+ router.get('/advanced/logs', controllers.admin.logs.get);
+ router.get('/advanced/post-cache', controllers.admin.postCache.get);
+
+ router.get('/development/logger', controllers.admin.logger.get);
+}
+
+module.exports = function(app, middleware, controllers) {
+ app.use('/admin/', adminRouter(middleware, controllers));
+ app.use('/api/admin/', apiRouter(middleware, controllers));
+};
diff --git a/src/topics.js b/src/topics.js
index 13522aad00..d4e5b38597 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -1,369 +1,369 @@
-"use strict";
-
-var async = require('async'),
- validator = require('validator'),
-
- _ = require('underscore'),
- db = require('./database'),
- posts = require('./posts'),
- utils = require('../public/src/utils'),
- plugins = require('./plugins'),
- user = require('./user'),
- categories = require('./categories'),
- privileges = require('./privileges');
-
-(function(Topics) {
-
- require('./topics/create')(Topics);
- require('./topics/delete')(Topics);
- require('./topics/unread')(Topics);
- require('./topics/recent')(Topics);
- require('./topics/popular')(Topics);
- require('./topics/user')(Topics);
- require('./topics/fork')(Topics);
- require('./topics/posts')(Topics);
- require('./topics/follow')(Topics);
- require('./topics/tags')(Topics);
- require('./topics/teaser')(Topics);
- require('./topics/suggested')(Topics);
-
- Topics.exists = function(tid, callback) {
- db.isSortedSetMember('topics:tid', tid, callback);
- };
-
- Topics.getTopicData = function(tid, callback) {
- db.getObject('topic:' + tid, function(err, topic) {
- if (err || !topic) {
- return callback(err);
- }
- modifyTopic(topic, callback);
- });
- };
-
- Topics.getTopicsData = function(tids, callback) {
- var keys = [];
-
- for (var i=0; i