diff --git a/README.md b/README.md index 4fc6d9aeb9..9062b049ae 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,75 @@ -# NodeBB - -[![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). - +# NodeBB + +[![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