mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-09 07:31:51 +02:00
merge
This commit is contained in:
15
NOTES.md
Normal file
15
NOTES.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## 0.4x Refactor Notes
|
||||
|
||||
Please remove this file after 0.4x (or perhaps organize it so that we can see the history of breaking changes)
|
||||
|
||||
### Immediate Deprecation Notices
|
||||
|
||||
* `action:ajaxifying` is no longer triggered on body but on window instead, in line with other similar hooks.
|
||||
* `filter:server.create_routes` and `filter:admin.create_routes` will have limited support (ajaxify works, but first-load will not). Please have a look at [this plugin](https://github.com/psychobunny/nodebb-plugin-kitchen-sink/blob/master/library.js#L16-L22) for an example on how to create routes in plugins from now on.
|
||||
|
||||
### Upcoming Deprecation Warnings
|
||||
|
||||
* `filter:footer.build` will be deprecated for 0.4x in favour of the widget system (WIP)
|
||||
* templates.setGlobal (server-side only) deprecated in favour of using res.locals
|
||||
* `plugins/fireHook` route will be deprecated for 0.4x
|
||||
* synchronous hooks will be deprecated for 0.4x - we're reducing complexity by removing the `callbacked: true` property in `plugin.json` - just use callbacks.
|
||||
43
app.js
43
app.js
@@ -95,13 +95,13 @@ function loadConfig() {
|
||||
nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
|
||||
}
|
||||
|
||||
|
||||
function start() {
|
||||
loadConfig();
|
||||
|
||||
nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path'));
|
||||
nconf.set('upload_url', path.join(path.sep, nconf.get('relative_path'), 'uploads', path.sep));
|
||||
nconf.set('base_dir', __dirname);
|
||||
nconf.set('views_dir', path.join(__dirname, 'public/templates'));
|
||||
|
||||
winston.info('Time: ' + new Date());
|
||||
winston.info('Initializing NodeBB v' + pkg.version);
|
||||
@@ -118,7 +118,6 @@ function start() {
|
||||
|
||||
require('./src/database').init(function(err) {
|
||||
meta.configs.init(function () {
|
||||
|
||||
var templates = require('./public/src/templates'),
|
||||
translator = require('./public/src/translator'),
|
||||
webserver = require('./src/webserver'),
|
||||
@@ -131,25 +130,28 @@ function start() {
|
||||
|
||||
upgrade.check(function(schema_ok) {
|
||||
if (schema_ok || nconf.get('check-schema') === false) {
|
||||
|
||||
sockets.init(webserver.server);
|
||||
|
||||
plugins.init();
|
||||
|
||||
translator.loadServer();
|
||||
|
||||
var customTemplates = meta.config['theme:templates'] ? path.join(nconf.get('themes_path'), meta.config['theme:id'], meta.config['theme:templates']) : false;
|
||||
|
||||
utils.walk(path.join(__dirname, 'public/templates'), function (err, tplsToLoad) {
|
||||
templates.init(tplsToLoad, customTemplates);
|
||||
});
|
||||
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-vanilla/templates'));
|
||||
nconf.set('theme_templates_path', meta.config['theme:templates'] ? path.join(nconf.get('themes_path'), meta.config['theme:id'], meta.config['theme:templates']) : nconf.get('base_templates_path'));
|
||||
|
||||
plugins.ready(function() {
|
||||
templates.ready(webserver.init);
|
||||
webserver.init();
|
||||
});
|
||||
|
||||
// Temporarily removed until ncb000gt/node-cron/issues/81 and ncb000gt/node-cron/issues/83 are fixed
|
||||
// notifications.init();
|
||||
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on('SIGHUP', restart);
|
||||
process.on('uncaughtException', function(err) {
|
||||
winston.error('[app] Encountered Uncaught Exception: ' + err.message);
|
||||
console.log(err.stack);
|
||||
restart();
|
||||
});
|
||||
} else {
|
||||
winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
|
||||
winston.warn(' node app --upgrade');
|
||||
@@ -238,6 +240,25 @@ function reset() {
|
||||
});
|
||||
}
|
||||
|
||||
function shutdown(code) {
|
||||
winston.info('[app] Shutdown (SIGTERM/SIGINT) Initialised.');
|
||||
require('./src/database').close();
|
||||
winston.info('[app] Database connection closed.');
|
||||
|
||||
winston.info('[app] Shutdown complete.');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
function restart() {
|
||||
if (process.send) {
|
||||
winston.info('[app] Restarting...');
|
||||
process.send('nodebb:restart');
|
||||
} else {
|
||||
winston.error('[app] Could not restart server. Shutting down.');
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
function displayHelp() {
|
||||
winston.info('Usage: node app [options] [arguments]');
|
||||
winston.info(' [NODE_ENV=development | NODE_ENV=production] node app [--start] [arguments]');
|
||||
|
||||
2
nodebb
2
nodebb
@@ -56,7 +56,7 @@ case "$1" in
|
||||
echo "Launching NodeBB in \"development\" mode."
|
||||
echo "To run the production build of NodeBB, please use \"forever\"."
|
||||
echo "More Information: https://github.com/designcreateplay/NodeBB/wiki/How-to-run-NodeBB"
|
||||
NODE_ENV=development supervisor -q --extensions 'node|js|tpl' -- app "$@"
|
||||
NODE_ENV=development supervisor -q --ignore public/templates --extensions 'node|js|tpl' -- app "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
|
||||
@@ -42,12 +42,14 @@
|
||||
"nodebb-plugin-mentions": "~0.4",
|
||||
"nodebb-plugin-markdown": "~0.4",
|
||||
"nodebb-widget-essentials": "~0.0",
|
||||
"nodebb-theme-vanilla": "~0.0.14",
|
||||
"nodebb-theme-vanilla": "~0.0.15",
|
||||
"nodebb-theme-cerulean": "~0.0.13",
|
||||
"nodebb-theme-lavender": "~0.0.22",
|
||||
"nodebb-theme-lavender": "~0.0.23",
|
||||
"less": "^1.6.3",
|
||||
"daemon": "~1.1.0",
|
||||
"underscore": "^1.6.0"
|
||||
"underscore": "^1.6.0",
|
||||
"mkdirp": "~0.3.5",
|
||||
"rimraf": "~2.2.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"redis": "0.8.3",
|
||||
|
||||
@@ -31,14 +31,12 @@ var ajaxify = {};
|
||||
ajaxify.initialLoad = false;
|
||||
|
||||
ajaxify.go = function (url, callback, quiet) {
|
||||
|
||||
// "quiet": If set to true, will not call pushState
|
||||
app.enterRoom('global');
|
||||
|
||||
$(window).off('scroll');
|
||||
|
||||
$(window).trigger('action:ajaxify.start', { url: url });
|
||||
$('body').trigger('action:ajaxifying', {url: url}); // Deprecated as of v0.4.0
|
||||
$(window).trigger('action:ajaxify.start', {url: url});
|
||||
|
||||
if ($('#content').hasClass('ajaxifying')) {
|
||||
templates.cancelRequest();
|
||||
@@ -50,19 +48,7 @@ var ajaxify = {};
|
||||
if (url.indexOf(RELATIVE_PATH.slice(1)) !== -1) {
|
||||
url = url.slice(RELATIVE_PATH.length);
|
||||
}
|
||||
|
||||
var tpl_url = templates.get_custom_map(url.split('?')[0]);
|
||||
|
||||
if (tpl_url == false && !templates[url]) {
|
||||
if (url === '' || url === '/') {
|
||||
tpl_url = 'home';
|
||||
} else {
|
||||
tpl_url = url.split('/')[0].split('?')[0];
|
||||
}
|
||||
|
||||
} else if (templates[url]) {
|
||||
tpl_url = url;
|
||||
}
|
||||
var tpl_url = ajaxify.getTemplateMapping(url);
|
||||
|
||||
var hash = '';
|
||||
if(ajaxify.initialLoad) {
|
||||
@@ -93,16 +79,11 @@ var ajaxify = {};
|
||||
|
||||
translator.load(tpl_url);
|
||||
|
||||
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
|
||||
ajaxify.fadeOut();
|
||||
|
||||
templates.flush();
|
||||
templates.load_template(function () {
|
||||
|
||||
require(['forum/' + tpl_url], function(script) {
|
||||
if (script && script.init) {
|
||||
script.init();
|
||||
}
|
||||
});
|
||||
ajaxify.loadScript(tpl_url);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
@@ -110,38 +91,12 @@ var ajaxify = {};
|
||||
|
||||
app.processPage();
|
||||
|
||||
var widgetLocations = [];
|
||||
ajaxify.renderWidgets(tpl_url, url, function(err) {
|
||||
ajaxify.fadeIn();
|
||||
ajaxify.initialLoad = false;
|
||||
|
||||
require(['vendor/async'], function(async) {
|
||||
$('#content [widget-area]').each(function() {
|
||||
widgetLocations.push($(this).attr('widget-area'));
|
||||
});
|
||||
|
||||
async.each(widgetLocations, function(location, next) {
|
||||
var area = $('#content [widget-area="' + location + '"]');
|
||||
|
||||
socket.emit('widgets.render', {template: tpl_url + '.tpl', url: url, location: location}, function(err, renderedWidgets) {
|
||||
area.html(templates.prepare(area.html()).parse({
|
||||
widgets: renderedWidgets
|
||||
})).removeClass('hidden');
|
||||
|
||||
if (!renderedWidgets.length) {
|
||||
$('body [no-widget-class]').each(function() {
|
||||
var $this = $(this);
|
||||
$this.removeClass();
|
||||
$this.addClass($this.attr('no-widget-class'));
|
||||
});
|
||||
}
|
||||
|
||||
next(err);
|
||||
});
|
||||
}, function(err) {
|
||||
$('#content, #footer').stop(true, true).removeClass('ajaxifying');
|
||||
ajaxify.initialLoad = false;
|
||||
|
||||
app.refreshTitle(url);
|
||||
$(window).trigger('action:ajaxify.end', { url: url });
|
||||
});
|
||||
app.refreshTitle(url);
|
||||
$(window).trigger('action:ajaxify.end', {url: url});
|
||||
});
|
||||
}, url);
|
||||
|
||||
@@ -151,6 +106,91 @@ var ajaxify = {};
|
||||
return false;
|
||||
};
|
||||
|
||||
ajaxify.loadScript = function(tpl_url, callback) {
|
||||
require(['forum/' + tpl_url], function(script) {
|
||||
if (script && script.init) {
|
||||
script.init();
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ajaxify.fadeIn = function() {
|
||||
$('#content, #footer').stop(true, true).removeClass('ajaxifying');
|
||||
};
|
||||
|
||||
ajaxify.fadeOut = function() {
|
||||
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
|
||||
};
|
||||
|
||||
ajaxify.getTemplateMapping = function(url) {
|
||||
var tpl_url = templates.get_custom_map(url.split('?')[0]);
|
||||
|
||||
if (tpl_url == false && !templates[url]) {
|
||||
if (url === '' || url === '/') {
|
||||
tpl_url = 'home';
|
||||
} else if (url === 'admin' || url === 'admin/') {
|
||||
tpl_url = 'admin/index';
|
||||
} else {
|
||||
tpl_url = url.split('/');
|
||||
|
||||
while(tpl_url.length) {
|
||||
if (templates.is_available(tpl_url.join('/'))) {
|
||||
tpl_url = tpl_url.join('/');
|
||||
break;
|
||||
}
|
||||
tpl_url.pop();
|
||||
}
|
||||
|
||||
if (!tpl_url.length) {
|
||||
tpl_url = url.split('/')[0].split('?')[0];
|
||||
}
|
||||
}
|
||||
} else if (templates[url]) {
|
||||
tpl_url = url;
|
||||
}
|
||||
|
||||
return tpl_url;
|
||||
}
|
||||
|
||||
ajaxify.renderWidgets = function(tpl_url, url, callback) {
|
||||
var widgetLocations = [];
|
||||
|
||||
require(['vendor/async'], function(async) {
|
||||
$('#content [widget-area]').each(function() {
|
||||
widgetLocations.push($(this).attr('widget-area'));
|
||||
});
|
||||
|
||||
async.each(widgetLocations, function(location, next) {
|
||||
var area = $('#content [widget-area="' + location + '"]');
|
||||
|
||||
socket.emit('widgets.render', {template: tpl_url + '.tpl', url: url, location: location}, function(err, renderedWidgets) {
|
||||
area.html(templates.prepare(area.html()).parse({
|
||||
widgets: renderedWidgets
|
||||
})).removeClass('hidden');
|
||||
|
||||
if (!renderedWidgets.length) {
|
||||
$('body [no-widget-class]').each(function() {
|
||||
var $this = $(this);
|
||||
$this.removeClass();
|
||||
$this.addClass($this.attr('no-widget-class'));
|
||||
});
|
||||
}
|
||||
|
||||
next(err);
|
||||
});
|
||||
}, function(err) {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
ajaxify.refresh = function() {
|
||||
ajaxify.go(ajaxify.currentPage);
|
||||
};
|
||||
|
||||
@@ -601,27 +601,6 @@ var socket,
|
||||
});
|
||||
};
|
||||
|
||||
$('document').ready(function () {
|
||||
$('#search-form').on('submit', function () {
|
||||
var input = $(this).find('input');
|
||||
ajaxify.go("search/" + input.val());
|
||||
input.val('');
|
||||
return false;
|
||||
});
|
||||
|
||||
$(window).blur(function(){
|
||||
app.isFocused = false;
|
||||
});
|
||||
|
||||
$(window).focus(function(){
|
||||
app.isFocused = true;
|
||||
|
||||
app.alternatingTitle('');
|
||||
});
|
||||
|
||||
createHeaderTooltips();
|
||||
});
|
||||
|
||||
function exposeConfigToTemplates() {
|
||||
$(document).ready(function() {
|
||||
templates.setGlobal('relative_path', RELATIVE_PATH);
|
||||
@@ -650,6 +629,45 @@ var socket,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$('document').ready(function () {
|
||||
var url = window.location.pathname.slice(1),
|
||||
tpl_url = ajaxify.getTemplateMapping(url);
|
||||
|
||||
$(window).trigger('action:ajaxify.start', {
|
||||
url: url
|
||||
});
|
||||
|
||||
$('#search-form').on('submit', function () {
|
||||
var input = $(this).find('input');
|
||||
ajaxify.go("search/" + input.val());
|
||||
input.val('');
|
||||
return false;
|
||||
});
|
||||
|
||||
$(window).blur(function(){
|
||||
app.isFocused = false;
|
||||
});
|
||||
|
||||
$(window).focus(function(){
|
||||
app.isFocused = true;
|
||||
|
||||
app.alternatingTitle('');
|
||||
});
|
||||
|
||||
createHeaderTooltips();
|
||||
templates.parseTemplateVariables();
|
||||
app.processPage();
|
||||
|
||||
ajaxify.renderWidgets(tpl_url, url);
|
||||
|
||||
ajaxify.loadScript(tpl_url, function() {
|
||||
$(window).trigger('action:ajaxify.end', {
|
||||
url: url
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
showWelcomeMessage = location.href.indexOf('loggedin') !== -1;
|
||||
|
||||
app.loadConfig();
|
||||
|
||||
@@ -74,9 +74,12 @@ define(['forum/accountheader'], function(header) {
|
||||
socket.emit('user.isOnline', theirid, Account.handleUserOnline);
|
||||
|
||||
socket.on('event:new_post', function(data) {
|
||||
var html = templates.prepare(templates['account'].blocks['posts']).parse(data);
|
||||
$('.user-recent-posts').prepend(html);
|
||||
$('.user-recent-posts span.timeago').timeago();
|
||||
templates.preload_template('account', function() {
|
||||
templates['account'].parse({posts:[]});
|
||||
var html = templates.prepare(templates['account'].blocks['posts']).parse(data);
|
||||
$('.user-recent-posts').prepend(html);
|
||||
$('.user-recent-posts span.timeago').timeago();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -34,20 +34,23 @@ define(['forum/accountheader'], function(header) {
|
||||
}
|
||||
|
||||
function onTopicsLoaded(posts) {
|
||||
var html = templates.prepare(templates['accountposts'].blocks['posts']).parse({
|
||||
posts: posts
|
||||
});
|
||||
templates.preload_template('accountposts', function() {
|
||||
templates['accountposts'].parse(posts: []);
|
||||
var html = templates.prepare(templates['accountposts'].blocks['posts']).parse({
|
||||
posts: posts
|
||||
});
|
||||
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
|
||||
$('#category-no-topics').remove();
|
||||
$('#category-no-topics').remove();
|
||||
|
||||
html = $(translatedHTML);
|
||||
html.find('img').addClass('img-responsive');
|
||||
$('.user-favourite-posts').append(html);
|
||||
$('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
html = $(translatedHTML);
|
||||
html.find('img').addClass('img-responsive');
|
||||
$('.user-favourite-posts').append(html);
|
||||
$('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -117,14 +117,17 @@ define(['uploader'], function(uploader) {
|
||||
timeout: 2000
|
||||
});
|
||||
|
||||
var html = templates.prepare(templates['admin/categories'].blocks['categories']).parse({
|
||||
categories: [data]
|
||||
});
|
||||
html = $(html);
|
||||
html.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker);
|
||||
templates.preload_template('admin/categories', function() {
|
||||
templates['admin/categories'].parse({categories:[]});
|
||||
var html = templates.prepare(templates['admin/categories'].blocks['categories']).parse({
|
||||
categories: [data]
|
||||
});
|
||||
html = $(html);
|
||||
html.find('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker);
|
||||
|
||||
$('#entry-container').append(html);
|
||||
$('#new-category-modal').modal('hide');
|
||||
$('#entry-container').append(html);
|
||||
$('#new-category-modal').modal('hide');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ define(function() {
|
||||
bootbox.confirm('Are you sure you wish to delete this group?', function(confirm) {
|
||||
if (confirm) {
|
||||
socket.emit('admin.groups.delete', gid, function(err, data) {
|
||||
console.log(err, data);
|
||||
if(err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
@@ -62,20 +62,23 @@ define(function() {
|
||||
var btnEl = $('#topics_loadmore');
|
||||
|
||||
if (topics.length > 0) {
|
||||
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
|
||||
topics: topics
|
||||
}),
|
||||
topicsListEl = $('.topics');
|
||||
templates.preload_template('admin/topics', function() {
|
||||
templates['admin/topics'].parse({topics:[]});
|
||||
var html = templates.prepare(templates['admin/topics'].blocks['topics']).parse({
|
||||
topics: topics
|
||||
}),
|
||||
topicsListEl = $('.topics');
|
||||
|
||||
// Fix relative paths
|
||||
html = html.replace(/\{relative_path\}/g, RELATIVE_PATH);
|
||||
// Fix relative paths
|
||||
html = html.replace(/\{relative_path\}/g, RELATIVE_PATH);
|
||||
|
||||
topicsListEl.html(topicsListEl.html() + html);
|
||||
topicsListEl.html(topicsListEl.html() + html);
|
||||
|
||||
Topics.resolveButtonStates();
|
||||
Topics.resolveButtonStates();
|
||||
|
||||
btnEl.html('Load More Topics');
|
||||
$('span.timeago').timeago();
|
||||
btnEl.html('Load More Topics');
|
||||
$('span.timeago').timeago();
|
||||
});
|
||||
} else {
|
||||
// Exhausted all topics
|
||||
btnEl.addClass('disabled');
|
||||
|
||||
@@ -190,27 +190,30 @@ define(function() {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
|
||||
users: data.users
|
||||
}),
|
||||
userListEl = document.querySelector('.users');
|
||||
templates.preload_template('admin/users', function() {
|
||||
templates['admin/users'].parse({users:[]});
|
||||
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
|
||||
users: data.users
|
||||
}),
|
||||
userListEl = document.querySelector('.users');
|
||||
|
||||
userListEl.innerHTML = html;
|
||||
$('.fa-spinner').addClass('none');
|
||||
userListEl.innerHTML = html;
|
||||
$('.fa-spinner').addClass('none');
|
||||
|
||||
if (data && data.users.length === 0) {
|
||||
$('#user-notfound-notify').html('User not found!')
|
||||
.show()
|
||||
.addClass('label-danger')
|
||||
.removeClass('label-success');
|
||||
} else {
|
||||
$('#user-notfound-notify').html(data.users.length + ' user' + (data.users.length > 1 ? 's' : '') + ' found! Search took ' + data.timing + ' ms.')
|
||||
.show()
|
||||
.addClass('label-success')
|
||||
.removeClass('label-danger');
|
||||
}
|
||||
if (data && data.users.length === 0) {
|
||||
$('#user-notfound-notify').html('User not found!')
|
||||
.show()
|
||||
.addClass('label-danger')
|
||||
.removeClass('label-success');
|
||||
} else {
|
||||
$('#user-notfound-notify').html(data.users.length + ' user' + (data.users.length > 1 ? 's' : '') + ' found! Search took ' + data.timing + ' ms.')
|
||||
.show()
|
||||
.addClass('label-success')
|
||||
.removeClass('label-danger');
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
updateButtons();
|
||||
});
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
@@ -220,14 +223,17 @@ define(function() {
|
||||
handleUserCreate();
|
||||
|
||||
function onUsersLoaded(users) {
|
||||
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
|
||||
users: users
|
||||
});
|
||||
html = $(html);
|
||||
$('#users-container').append(html);
|
||||
templates.preload_template('admin/users', function() {
|
||||
templates['admin/users'].parse({users:[]});
|
||||
var html = templates.prepare(templates['admin/users'].blocks['users']).parse({
|
||||
users: users
|
||||
});
|
||||
html = $(html);
|
||||
$('#users-container').append(html);
|
||||
|
||||
updateUserBanButtons(html.find('.ban-btn'));
|
||||
updateUserAdminButtons(html.find('.admin-btn'));
|
||||
updateUserBanButtons(html.find('.ban-btn'));
|
||||
updateUserAdminButtons(html.find('.admin-btn'));
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreUsers() {
|
||||
|
||||
@@ -162,45 +162,47 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
|
||||
|
||||
Category.onNewTopic = function(data) {
|
||||
$(window).trigger('filter:categories.new_topic', data);
|
||||
templates.preload_template('category', function() {
|
||||
templates['category'].parse({topics:[]});
|
||||
var html = templates.prepare(templates['category'].blocks['topics']).parse({
|
||||
topics: [data]
|
||||
});
|
||||
|
||||
var html = templates.prepare(templates['category'].blocks['topics']).parse({
|
||||
topics: [data]
|
||||
});
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
var topic = $(translatedHTML),
|
||||
container = $('#topics-container'),
|
||||
topics = $('#topics-container').children('.category-item'),
|
||||
numTopics = topics.length;
|
||||
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
var topic = $(translatedHTML),
|
||||
container = $('#topics-container'),
|
||||
topics = $('#topics-container').children('.category-item'),
|
||||
numTopics = topics.length;
|
||||
$('#topics-container, .category-sidebar').removeClass('hidden');
|
||||
$('#category-no-topics').remove();
|
||||
|
||||
$('#topics-container, .category-sidebar').removeClass('hidden');
|
||||
$('#category-no-topics').remove();
|
||||
|
||||
if (numTopics > 0) {
|
||||
for (var x = 0; x < numTopics; x++) {
|
||||
if ($(topics[x]).find('.fa-thumb-tack').length) {
|
||||
if(x === numTopics - 1) {
|
||||
topic.insertAfter(topics[x]);
|
||||
if (numTopics > 0) {
|
||||
for (var x = 0; x < numTopics; x++) {
|
||||
if ($(topics[x]).find('.fa-thumb-tack').length) {
|
||||
if(x === numTopics - 1) {
|
||||
topic.insertAfter(topics[x]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
topic.insertBefore(topics[x]);
|
||||
break;
|
||||
}
|
||||
topic.insertBefore(topics[x]);
|
||||
break;
|
||||
} else {
|
||||
container.append(topic);
|
||||
}
|
||||
} else {
|
||||
container.append(topic);
|
||||
}
|
||||
|
||||
topic.hide().fadeIn('slow');
|
||||
topic.hide().fadeIn('slow');
|
||||
|
||||
socket.emit('categories.getPageCount', templates.get('category_id'), function(err, newPageCount) {
|
||||
pagination.recreatePaginationLinks(newPageCount);
|
||||
socket.emit('categories.getPageCount', templates.get('category_id'), function(err, newPageCount) {
|
||||
pagination.recreatePaginationLinks(newPageCount);
|
||||
});
|
||||
|
||||
topic.find('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
|
||||
$(window).trigger('action:categories.new_topic.loaded');
|
||||
});
|
||||
|
||||
topic.find('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
|
||||
$(window).trigger('action:categories.new_topic.loaded');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -239,36 +241,39 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
|
||||
|
||||
findInsertionPoint();
|
||||
|
||||
var html = templates.prepare(templates['category'].blocks['topics']).parse({
|
||||
topics: topics
|
||||
});
|
||||
templates.preload_template('category', function() {
|
||||
templates['category'].parse({topics:[]});
|
||||
var html = templates.prepare(templates['category'].blocks['topics']).parse({
|
||||
topics: topics
|
||||
});
|
||||
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
var container = $('#topics-container'),
|
||||
html = $(translatedHTML);
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
var container = $('#topics-container'),
|
||||
html = $(translatedHTML);
|
||||
|
||||
$('#topics-container, .category-sidebar').removeClass('hidden');
|
||||
$('#category-no-topics').remove();
|
||||
$('#topics-container, .category-sidebar').removeClass('hidden');
|
||||
$('#category-no-topics').remove();
|
||||
|
||||
if(config.usePagination) {
|
||||
container.empty().append(html);
|
||||
} else {
|
||||
if(after) {
|
||||
html.insertAfter(after);
|
||||
} else if(before) {
|
||||
html.insertBefore(before);
|
||||
if(config.usePagination) {
|
||||
container.empty().append(html);
|
||||
} else {
|
||||
container.append(html);
|
||||
if(after) {
|
||||
html.insertAfter(after);
|
||||
} else if(before) {
|
||||
html.insertBefore(before);
|
||||
} else {
|
||||
container.append(html);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.find('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
html.find('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(topics);
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
callback(topics);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -33,20 +33,23 @@ define(['forum/accountheader'], function(header) {
|
||||
}
|
||||
|
||||
function onTopicsLoaded(posts) {
|
||||
var html = templates.prepare(templates['favourites'].blocks['posts']).parse({
|
||||
posts: posts
|
||||
});
|
||||
templates.preload_template('favourites', function() {
|
||||
templates['favourites'].parse({posts:[]});
|
||||
var html = templates.prepare(templates['favourites'].blocks['posts']).parse({
|
||||
posts: posts
|
||||
});
|
||||
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
|
||||
$('#category-no-topics').remove();
|
||||
$('#category-no-topics').remove();
|
||||
|
||||
html = $(translatedHTML);
|
||||
html.find('img').addClass('img-responsive');
|
||||
$('.user-favourite-posts').append(html);
|
||||
$('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
html = $(translatedHTML);
|
||||
html.find('img').addClass('img-responsive');
|
||||
$('.user-favourite-posts').append(html);
|
||||
$('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -116,19 +116,22 @@ define(function() {
|
||||
}
|
||||
|
||||
Recent.onTopicsLoaded = function(template, topics) {
|
||||
var html = templates.prepare(templates[template].blocks['topics']).parse({
|
||||
topics: topics
|
||||
});
|
||||
templates.preload_template(template, function() {
|
||||
templates[template].parse({topics:[]});
|
||||
var html = templates.prepare(templates[template].blocks['topics']).parse({
|
||||
topics: topics
|
||||
});
|
||||
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
translator.translate(html, function(translatedHTML) {
|
||||
|
||||
$('#category-no-topics').remove();
|
||||
$('#category-no-topics').remove();
|
||||
|
||||
html = $(translatedHTML);
|
||||
$('#topics-container').append(html);
|
||||
$('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
html = $(translatedHTML);
|
||||
$('#topics-container').append(html);
|
||||
$('span.timeago').timeago();
|
||||
app.createUserTooltips();
|
||||
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1207,6 +1207,9 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
|
||||
|
||||
findInsertionPoint();
|
||||
|
||||
data.title = templates.get('topic_name');
|
||||
data.viewcount = templates.get('viewcount');
|
||||
|
||||
parseAndTranslatePosts(data, function(translatedHTML) {
|
||||
var translated = $(translatedHTML);
|
||||
|
||||
@@ -1233,8 +1236,11 @@ define(['composer', 'forum/pagination'], function(composer, pagination) {
|
||||
}
|
||||
|
||||
function parseAndTranslatePosts(data, callback) {
|
||||
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
|
||||
translator.translate(html, callback);
|
||||
templates.preload_template('topic', function() {
|
||||
templates['topic'].parse({posts: []});
|
||||
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
|
||||
translator.translate(html, callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -59,22 +59,24 @@ define(function() {
|
||||
return;
|
||||
}
|
||||
|
||||
var html = templates.prepare(templates['users'].blocks['users']).parse({
|
||||
users: data.users
|
||||
}),
|
||||
userListEl = $('#users-container');
|
||||
templates.preload_template('users', function() {
|
||||
templates['users'].parse({users:[]});
|
||||
var html = templates.prepare(templates['users'].blocks['users']).parse({
|
||||
users: data.users
|
||||
}),
|
||||
userListEl = $('#users-container');
|
||||
|
||||
userListEl.html(html);
|
||||
userListEl.html(html);
|
||||
|
||||
|
||||
if (data && data.users.length === 0) {
|
||||
$('#user-notfound-notify').html('User not found!');
|
||||
$('#user-notfound-notify').parent().addClass('btn-warning label-warning');
|
||||
} else {
|
||||
$('#user-notfound-notify').html(data.users.length + ' user' + (data.users.length > 1 ? 's' : '') + ' found! Search took ' + data.timing + ' ms.');
|
||||
$('#user-notfound-notify').parent().addClass('btn-success label-success');
|
||||
}
|
||||
|
||||
if (data && data.users.length === 0) {
|
||||
$('#user-notfound-notify').html('User not found!');
|
||||
$('#user-notfound-notify').parent().addClass('btn-warning label-warning');
|
||||
} else {
|
||||
$('#user-notfound-notify').html(data.users.length + ' user' + (data.users.length > 1 ? 's' : '') + ' found! Search took ' + data.timing + ' ms.');
|
||||
$('#user-notfound-notify').parent().addClass('btn-success label-success');
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 500); //replace this with global throttling function/constant
|
||||
|
||||
@@ -108,17 +110,20 @@ define(function() {
|
||||
}
|
||||
|
||||
function onUsersLoaded(users, emptyContainer) {
|
||||
var html = templates.prepare(templates['users'].blocks['users']).parse({
|
||||
users: users
|
||||
});
|
||||
templates.preload_template('users', function() {
|
||||
templates['useres'].parse({users:[]});
|
||||
var html = templates.prepare(templates['users'].blocks['users']).parse({
|
||||
users: users
|
||||
});
|
||||
|
||||
translator.translate(html, function(translated) {
|
||||
if(emptyContainer) {
|
||||
$('#users-container .registered-user').remove();
|
||||
}
|
||||
translator.translate(html, function(translated) {
|
||||
if(emptyContainer) {
|
||||
$('#users-container .registered-user').remove();
|
||||
}
|
||||
|
||||
$('#users-container').append(translated);
|
||||
$('#users-container .anon-user').appendTo($('#users-container'));
|
||||
$('#users-container').append(translated);
|
||||
$('#users-container .anon-user').appendTo($('#users-container'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,43 +17,27 @@
|
||||
path = require('path');
|
||||
} catch (e) {}
|
||||
|
||||
templates.force_refresh = function (tpl) {
|
||||
templates.force_refresh = function(tpl) {
|
||||
return !!config.force_refresh[tpl];
|
||||
};
|
||||
|
||||
templates.get_custom_map = function (tpl) {
|
||||
if (config['custom_mapping'] && tpl) {
|
||||
for (var pattern in config['custom_mapping']) {
|
||||
templates.get_custom_map = function(tpl) {
|
||||
if (config.custom_mapping && tpl) {
|
||||
for (var pattern in config.custom_mapping) {
|
||||
if (tpl.match(pattern)) {
|
||||
return (config['custom_mapping'][pattern]);
|
||||
return (config.custom_mapping[pattern]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
templates.is_available = function (tpl) {
|
||||
return $.inArray(tpl, available_templates) !== -1;
|
||||
};
|
||||
|
||||
templates.ready = function (callback) {
|
||||
if (callback == null) {
|
||||
if (this.ready_callback) {
|
||||
this.ready_callback();
|
||||
} else {
|
||||
this.loaded = true;
|
||||
}
|
||||
} else {
|
||||
if (this.loaded == true) {
|
||||
callback();
|
||||
} else {
|
||||
this.ready_callback = callback;
|
||||
}
|
||||
}
|
||||
templates.is_available = function(tpl) {
|
||||
return $.inArray(tpl + '.tpl', available_templates) !== -1;
|
||||
};
|
||||
|
||||
templates.prepare = function (raw_tpl) {
|
||||
templates.prepare = function(raw_tpl) {
|
||||
var template = {};
|
||||
template.html = raw_tpl;
|
||||
template.parse = parse;
|
||||
@@ -62,73 +46,6 @@
|
||||
return template;
|
||||
};
|
||||
|
||||
function loadTemplates(templatesToLoad, customTemplateDir) {
|
||||
function loadServer() {
|
||||
var loaded = templatesToLoad.length,
|
||||
templatesPath = __dirname + '/../templates';
|
||||
|
||||
function getTemplates(directory) {
|
||||
for (var t in templatesToLoad) {
|
||||
(function (file) {
|
||||
function loadFile(html) {
|
||||
var template = function () {
|
||||
this.toString = function () {
|
||||
return this.html;
|
||||
};
|
||||
}
|
||||
|
||||
template.prototype.file = file;
|
||||
template.prototype.parse = parse;
|
||||
template.prototype.html = String(html);
|
||||
|
||||
templates[file] = new template;
|
||||
|
||||
loaded--;
|
||||
if (loaded === 0) {
|
||||
templates.ready();
|
||||
}
|
||||
}
|
||||
|
||||
fs.readFile(directory + '/' + file + '.tpl', function (err, html) {
|
||||
if (err && directory !== templatesPath) {
|
||||
fs.readFile(templatesPath + '/' + file + '.tpl', function (err, html) {
|
||||
loadFile(html);
|
||||
});
|
||||
} else {
|
||||
loadFile(html);
|
||||
}
|
||||
});
|
||||
}(templatesToLoad[t]));
|
||||
}
|
||||
}
|
||||
if (customTemplateDir) {
|
||||
fs.exists(customTemplateDir, function (exists) {
|
||||
var directory = (exists ? customTemplateDir : templatesPath);
|
||||
getTemplates(directory);
|
||||
});
|
||||
} else {
|
||||
getTemplates(templatesPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function loadClient() {
|
||||
$.when($.getJSON(RELATIVE_PATH + '/templates/config.json'), $.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function (config_data, templates_data) {
|
||||
config = config_data[0];
|
||||
available_templates = templates_data[0];
|
||||
templates.ready();
|
||||
});
|
||||
}
|
||||
|
||||
if (fs === null) loadClient();
|
||||
else loadServer();
|
||||
}
|
||||
|
||||
|
||||
templates.init = function (templates_to_load, custom_templates) {
|
||||
loadTemplates(templates_to_load || [], custom_templates || false);
|
||||
};
|
||||
|
||||
templates.render = function(filename, options, fn) {
|
||||
if ('function' === typeof options) {
|
||||
fn = options, options = false;
|
||||
@@ -139,9 +56,8 @@
|
||||
.replace('.' + options.settings['view engine'], '');
|
||||
|
||||
if (!templates[tpl]) {
|
||||
fs.readFile(filename, function (err, html) {
|
||||
templates[tpl] = html.toString();
|
||||
templates.prepare(templates[tpl]);
|
||||
fs.readFile(filename, function(err, html) {
|
||||
templates[tpl] = templates.prepare(html.toString());
|
||||
|
||||
return fn(err, templates[tpl].parse(options));
|
||||
});
|
||||
@@ -150,19 +66,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
templates.getTemplateNameFromUrl = function (url) {
|
||||
var parts = url.split('?')[0].split('/');
|
||||
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
if (templates.is_available(parts[i])) {
|
||||
return parts[i];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
templates.preload_template = function(tpl_name, callback) {
|
||||
|
||||
if(templates[tpl_name]) {
|
||||
return callback();
|
||||
}
|
||||
@@ -171,9 +75,9 @@
|
||||
// should be named something else
|
||||
// TODO: The "Date.now()" in the line below is only there for development purposes.
|
||||
// It should be removed at some point.
|
||||
$.get(RELATIVE_PATH + '/templates/' + tpl_name + '.tpl?v=' + Date.now(), function (html) {
|
||||
var template = function () {
|
||||
this.toString = function () {
|
||||
$.get(RELATIVE_PATH + '/templates/' + tpl_name + '.tpl?v=' + Date.now(), function(html) {
|
||||
var template = function() {
|
||||
this.toString = function() {
|
||||
return this.html;
|
||||
};
|
||||
};
|
||||
@@ -188,13 +92,13 @@
|
||||
});
|
||||
};
|
||||
|
||||
templates.load_template = function (callback, url, template) {
|
||||
templates.load_template = function(callback, url, template) {
|
||||
var location = document.location || window.location,
|
||||
api_url = (url === '' || url === '/') ? 'home' : url,
|
||||
tpl_url = templates.get_custom_map(api_url.split('?')[0]);
|
||||
|
||||
if (!tpl_url) {
|
||||
tpl_url = templates.getTemplateNameFromUrl(api_url);
|
||||
tpl_url = ajaxify.getTemplateMapping(api_url);
|
||||
}
|
||||
|
||||
var template_data = null;
|
||||
@@ -210,7 +114,7 @@
|
||||
apiXHR = $.ajax({
|
||||
url: RELATIVE_PATH + '/api/' + api_url,
|
||||
cache: false,
|
||||
success: function (data) {
|
||||
success: function(data) {
|
||||
if (!data) {
|
||||
ajaxify.go('404');
|
||||
return;
|
||||
@@ -219,7 +123,7 @@
|
||||
template_data = data;
|
||||
parse_template();
|
||||
},
|
||||
error: function (data, textStatus) {
|
||||
error: function(data, textStatus) {
|
||||
$('#content, #footer').stop(true, true).removeClass('ajaxifying');
|
||||
if (data && data.status == 404) {
|
||||
return ajaxify.go('404');
|
||||
@@ -234,40 +138,45 @@
|
||||
function parse_template() {
|
||||
if (!templates[tpl_url] || !template_data) return;
|
||||
|
||||
if (typeof global !== "undefined")
|
||||
if (typeof global !== "undefined") {
|
||||
template_data['relative_path'] = nconf.get('relative_path');
|
||||
else
|
||||
} else {
|
||||
template_data['relative_path'] = RELATIVE_PATH;
|
||||
}
|
||||
|
||||
translator.translate(templates[tpl_url].parse(template_data), function (translatedTemplate) {
|
||||
var template = templates[tpl_url].parse(template_data)
|
||||
|
||||
translator.translate(template, function(translatedTemplate) {
|
||||
$('#content').html(translatedTemplate);
|
||||
|
||||
$('#content [template-variable]').each(function (index, element) {
|
||||
var value = null;
|
||||
|
||||
switch ($(element).attr('template-type')) {
|
||||
case 'boolean':
|
||||
value = ($(element).val() === 'true' || $(element).val() === '1') ? true : false;
|
||||
break;
|
||||
case 'int': // Intentional fall-through
|
||||
case 'integer':
|
||||
value = parseInt($(element).val());
|
||||
break;
|
||||
default:
|
||||
value = $(element).val();
|
||||
break;
|
||||
}
|
||||
|
||||
templates.set($(element).attr('template-variable'), value);
|
||||
});
|
||||
templates.parseTemplateVariables();
|
||||
|
||||
if (callback) {
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
templates.parseTemplateVariables = function() {
|
||||
$('#content [template-variable]').each(function(index, element) {
|
||||
var value = null;
|
||||
|
||||
switch ($(element).attr('template-type')) {
|
||||
case 'boolean':
|
||||
value = ($(element).val() === 'true' || $(element).val() === '1') ? true : false;
|
||||
break;
|
||||
case 'int':
|
||||
case 'integer':
|
||||
value = parseInt($(element).val());
|
||||
break;
|
||||
default:
|
||||
value = $(element).val();
|
||||
break;
|
||||
}
|
||||
|
||||
templates.set($(element).attr('template-variable'), value);
|
||||
});
|
||||
};
|
||||
|
||||
templates.cancelRequest = function() {
|
||||
@@ -276,15 +185,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
templates.flush = function () {
|
||||
templates.flush = function() {
|
||||
parsed_variables = {};
|
||||
};
|
||||
|
||||
templates.get = function (key) {
|
||||
templates.get = function(key) {
|
||||
return parsed_variables[key];
|
||||
};
|
||||
|
||||
templates.set = function (key, value) {
|
||||
templates.set = function(key, value) {
|
||||
parsed_variables[key] = value;
|
||||
};
|
||||
|
||||
@@ -293,7 +202,7 @@
|
||||
};
|
||||
|
||||
//modified from https://github.com/psychobunny/dcp.templates
|
||||
var parse = function (data) {
|
||||
var parse = function(data) {
|
||||
var self = this;
|
||||
|
||||
function replace(key, value, template) {
|
||||
@@ -332,7 +241,6 @@
|
||||
var template = this.html,
|
||||
regex, block;
|
||||
|
||||
// registering globals
|
||||
for (var g in templates.globals) {
|
||||
if (templates.globals.hasOwnProperty(g)) {
|
||||
data[g] = data[g] || templates.globals[g];
|
||||
@@ -444,7 +352,13 @@
|
||||
|
||||
if ('undefined' !== typeof window) {
|
||||
window.templates = module.exports;
|
||||
templates.init();
|
||||
|
||||
window.onload = function() {
|
||||
$.when($.getJSON(RELATIVE_PATH + '/templates/config.json'), $.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function (config_data, templates_data) {
|
||||
config = config_data[0];
|
||||
available_templates = templates_data[0];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})('undefined' === typeof module ? {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
(function (module) {
|
||||
"use strict";
|
||||
/*global RELATIVE_PATH*/
|
||||
/*global RELATIVE_PATH, config*/
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
*
|
||||
* 1. recursion needed when parsing language keys (ex. topics:modal.delete.title), right now json is all one level deep
|
||||
* 2. user side settings for preferred language
|
||||
*
|
||||
* user side settings for preferred language
|
||||
*/
|
||||
|
||||
var translator = {},
|
||||
@@ -98,8 +95,9 @@
|
||||
};
|
||||
|
||||
translator.translate = function (data, callback) {
|
||||
var keys = data.match(/\[\[.*?\]\]/g),
|
||||
loading = 0;
|
||||
if (!data) {
|
||||
return callback(data);
|
||||
}
|
||||
|
||||
function insertLanguage(text, key, value, variables) {
|
||||
if (value) {
|
||||
@@ -117,6 +115,9 @@
|
||||
return text;
|
||||
}
|
||||
|
||||
var keys = data.match(/\[\[.*?\]\]/g),
|
||||
loading = 0;
|
||||
|
||||
for (var key in keys) {
|
||||
if (keys.hasOwnProperty(key)) {
|
||||
keys[key] = '' + keys[key];
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
|
||||
//Adapted from http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
|
||||
walk: function(dir, done) {
|
||||
var results = [],
|
||||
path = require('path'),
|
||||
main_dir = path.join(__dirname, '..', 'templates');
|
||||
var results = [];
|
||||
|
||||
fs.readdir(dir, function(err, list) {
|
||||
if (err) {
|
||||
@@ -51,7 +49,7 @@
|
||||
}
|
||||
});
|
||||
} else {
|
||||
results.push(file.replace(main_dir + '/', '').replace('.tpl', ''));
|
||||
results.push(file);
|
||||
if (!--pending) {
|
||||
done(null, results);
|
||||
}
|
||||
|
||||
1
public/templates/.gitignore
vendored
Normal file
1
public/templates/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.tpl
|
||||
@@ -1,4 +0,0 @@
|
||||
<div class="alert alert-danger">
|
||||
<strong>[[global:403.title]]</strong>
|
||||
<p>[[global:403.message]]</p>
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
<div class="alert alert-danger">
|
||||
<strong>[[global:404.title]]</strong>
|
||||
<p>[[global:404.message]]</p>
|
||||
</div>
|
||||
@@ -1,5 +0,0 @@
|
||||
<div class="alert alert-danger">
|
||||
<strong>[[global:500.title]]</strong>
|
||||
<p>[[global:500.message]]</p>
|
||||
<p>{errorMessage}</p>
|
||||
</div>
|
||||
@@ -1,165 +0,0 @@
|
||||
|
||||
<div class="account-username-box" data-userslug="{userslug}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="account">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5 account-block">
|
||||
|
||||
<div class="text-center account-picture-block panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<a href="{relative_path}/user/{userslug}"><img src="{picture}" class="user-profile-picture img-thumbnail"/></a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<span>
|
||||
<i class="account-online-status fa fa-circle status offline" title="[[global:{status}]]"></i>
|
||||
<span class="account-username"> {username}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- IF banned -->
|
||||
<div>
|
||||
<span class="label label-danger">[[user:banned]]</span>
|
||||
</div>
|
||||
<!-- ENDIF banned -->
|
||||
<div>
|
||||
<a id="chat-btn" href="#" class="btn btn-primary hide">[[user:chat]]</a>
|
||||
<a id="follow-btn" href="#" class="btn btn-success hide">[[user:follow]]</a>
|
||||
<a id="unfollow-btn" href="#" class="btn btn-warning hide">[[user:unfollow]]</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center account-stats panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="inline-block text-center">
|
||||
<div class="human-readable-number" title="{reputation}">{reputation}</div>
|
||||
<div class="account-bio-label">[[user:reputation]]</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-block text-center">
|
||||
<div class="human-readable-number" title="{postcount}">{postcount}</div>
|
||||
<div class="account-bio-label">[[user:posts]]</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-block text-center">
|
||||
<div class="human-readable-number" title="{profileviews}">{profileviews}</div>
|
||||
<div class="account-bio-label">[[user:profile_views]]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
|
||||
<!-- IF email -->
|
||||
<span class="account-bio-label">[[user:email]]</span>
|
||||
<span class="account-bio-value"><i class="fa fa-eye-slash {emailClass}" title="[[user:email_hidden]]"></i> {email}</span>
|
||||
<!-- ENDIF email -->
|
||||
|
||||
<!-- IF fullname -->
|
||||
<span class="account-bio-label">[[user:fullname]]</span>
|
||||
<span class="account-bio-value">{fullname}</span>
|
||||
<!-- ENDIF fullname -->
|
||||
|
||||
<!-- IF websiteName -->
|
||||
<span class="account-bio-label">[[user:website]]</span>
|
||||
<span class="account-bio-value"><a href="{website}">{websiteName}</a></span>
|
||||
<!-- ENDIF websiteName -->
|
||||
|
||||
<!-- IF location -->
|
||||
<span class="account-bio-label">[[user:location]]</span>
|
||||
<span class="account-bio-value">{location}</span>
|
||||
<!-- ENDIF location -->
|
||||
|
||||
<!-- IF age -->
|
||||
<span class="account-bio-label">[[user:age]]</span>
|
||||
<span class="account-bio-value">{age}</span>
|
||||
<!-- ENDIF age -->
|
||||
|
||||
|
||||
<span class="account-bio-label">[[user:followers]]</span>
|
||||
<span class="human-readable-number account-bio-value" title="{followerCount}">{followerCount}</span>
|
||||
|
||||
<span class="account-bio-label">[[user:following]]</span>
|
||||
<span class="human-readable-number account-bio-value" title="{followingCount}">{followingCount}</span>
|
||||
|
||||
<span class="account-bio-label">[[user:joined]]</span>
|
||||
<span class="timeago account-bio-value" title="{joindate}"></span>
|
||||
|
||||
<span class="account-bio-label">[[user:lastonline]]</span>
|
||||
<span class="timeago account-bio-value" title="{lastonline}"></span>
|
||||
|
||||
<!-- IF !disableSignatures -->
|
||||
<!-- IF signature -->
|
||||
<hr/>
|
||||
<span class="account-bio-label">[[user:signature]]</span>
|
||||
<div class="post-signature">
|
||||
<span id='signature'>{signature}</span>
|
||||
</div>
|
||||
<!-- ENDIF signature -->
|
||||
<!-- ENDIF !disableSignatures -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IF ips.length -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">[[global:recentips]]</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- BEGIN ips -->
|
||||
<div>{ips.ip}</div>
|
||||
<!-- END ips -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- ENDIF ips.length -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-7 user-recent-posts">
|
||||
<div class="topic-row panel panel-default clearfix">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">[[global:recentposts]]</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- IF !posts.length -->
|
||||
<span>[[user:has_no_posts]]</span>
|
||||
<!-- ENDIF !posts.length -->
|
||||
<!-- BEGIN posts -->
|
||||
<div class="clearfix">
|
||||
<p>{posts.content}</p>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
<a href="../../topic/{posts.tid}/#{posts.pid}">[[global:posted]]</a>
|
||||
[[global:in]]
|
||||
<a href="../../category/{posts.category.slug}">
|
||||
<i class="fa {posts.category.icon}"></i> {posts.category.name}
|
||||
</a>
|
||||
<span class="timeago" title="{posts.relativeTime}"></span>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
<hr/>
|
||||
<!-- END posts -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div id="user-action-alert" class="alert alert-success hide"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
<input type="hidden" template-type="boolean" template-variable="isFollowing" value="{isFollowing}" />
|
||||
@@ -1,156 +0,0 @@
|
||||
<div class="account-username-box" data-userslug="{userslug}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="account">
|
||||
|
||||
<div id="change-picture-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="[[user:change_picture]]" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="myModalLabel">[[user:change_picture]]</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="gravatar-box">
|
||||
<img id="user-gravatar-picture" src="" class="img-thumbnail user-profile-picture">
|
||||
<span class="user-picture-label">[[user:gravatar]]</span>
|
||||
<i class='fa fa-check fa-2x'></i>
|
||||
</div>
|
||||
<br/>
|
||||
<div id="uploaded-box">
|
||||
<img id="user-uploaded-picture" src="" class="img-thumbnail user-profile-picture">
|
||||
<span class="user-picture-label">[[user:uploaded_picture]]</span>
|
||||
<i class='fa fa-check fa-2x'></i>
|
||||
</div>
|
||||
|
||||
<a id="uploadPictureBtn" href="#">[[user:upload_new_picture]]</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">[[global:close]]</button>
|
||||
<button id="savePictureChangesBtn" class="btn btn-primary">[[global:save_changes]]</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2" style="text-align: center; margin-bottom:20px;">
|
||||
<div class="account-picture-block text-center">
|
||||
<img id="user-current-picture" class="user-profile-picture img-thumbnail" src="{picture}" /><br /><br />
|
||||
<a id="changePictureBtn" href="#" class="btn btn-primary">[[user:change_picture]]</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<div>
|
||||
<form class='form-horizontal'>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputUsername">[[user:username]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="text" id="inputUsername" placeholder="Username" value="{username}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputEmail">[[user:email]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="text" id="inputEmail" placeholder="Email" value="{email}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputFullname">[[user:fullname]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="text" id="inputFullname" placeholder="Full Name" value="{fullname}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputWebsite">[[user:website]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="text" id="inputWebsite" placeholder="http://website.com" value="{website}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputLocation">[[user:location]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="text" id="inputLocation" placeholder="Location" value="{location}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputBirthday">[[user:birthday]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="date" id="inputBirthday" placeholder="mm/dd/yyyy" value="{birthday}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IF !disableSignatures -->
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputSignature">[[user:signature]]</label> <small><label id="signatureCharCountLeft"></label></small>
|
||||
<div class="controls">
|
||||
<textarea class="form-control" id="inputSignature" rows="5">{signature}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ENDIF !disableSignatures -->
|
||||
|
||||
<input type="hidden" id="inputUID" value="{uid}"><br />
|
||||
|
||||
<div class="form-actions">
|
||||
<a id="submitBtn" href="#" class="btn btn-primary">[[global:save_changes]]</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr class="visible-xs visible-sm"/>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<div style="vertical-align:top;">
|
||||
<form class='form-horizontal'>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputCurrentPassword">[[user:current_password]]</label>
|
||||
<div class="controls">
|
||||
<input class="form-control" type="password" id="inputCurrentPassword" placeholder="Current Password" value=""<!-- IF !hasPassword --> disabled<!-- ENDIF !hasPassword-->>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputNewPassword">[[user:password]]</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="password" id="inputNewPassword" placeholder="New Password" value="">
|
||||
<span class="input-group-addon">
|
||||
<span id="password-notify"><i class="fa fa-circle-o"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputNewPasswordAgain">[[user:confirm_password]]</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="password" id="inputNewPasswordAgain" placeholder="Confirm Password" value="">
|
||||
<span class="input-group-addon">
|
||||
<span id="password-confirm-notify"><i class="fa fa-circle-o"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-actions">
|
||||
<a id="changePasswordBtn" href="#" class="btn btn-primary">[[user:change_password]]</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
<input type="hidden" template-variable="gravatarpicture" value="{gravatarpicture}" />
|
||||
<input type="hidden" template-variable="uploadedpicture" value="{uploadedpicture}" />
|
||||
@@ -1,45 +0,0 @@
|
||||
<div class="account-username-box clearfix" data-userslug="{userslug}" data-uid="{uid}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="favourites">
|
||||
|
||||
<!-- IF !posts.length -->
|
||||
<div class="alert alert-warning">[[user:has_no_posts]]</div>
|
||||
<!-- ENDIF !posts.length -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 user-favourite-posts" data-nextstart="{nextStart}">
|
||||
<!-- BEGIN posts -->
|
||||
<div class="topic-row panel panel-default clearfix">
|
||||
<div class="panel-body">
|
||||
<a href="../../user/{posts.userslug}">
|
||||
<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}">
|
||||
</a>
|
||||
|
||||
<a href="../../user/{posts.userslug}">
|
||||
<strong><span>{posts.username}</span></strong>
|
||||
</a>
|
||||
<p>{posts.content}</p>
|
||||
|
||||
<div>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
<a href="../../topic/{posts.topic.slug}#{posts.pid}">[[global:posted]]</a>
|
||||
[[global:in]]
|
||||
<a href="../../category/{posts.category.slug}">
|
||||
<i class="fa {posts.category.icon}"></i> {posts.category.name}
|
||||
</a>
|
||||
<span class="timeago" title="{posts.relativeTime}"></span>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
@@ -1,43 +0,0 @@
|
||||
<div class="account-username-box clearfix" data-userslug="{userslug}" data-uid="{uid}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="account">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h4>[[global:privacy]]</h4>
|
||||
<div class="well">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-property="showemail" /> <strong>[[user:show_email]]</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<h4>[[global:pagination]]</h4>
|
||||
<div class="well">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-property="usePagination"> <strong>[[user:paginate_description]]</strong>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<strong>[[user:topics_per_page]]</strong><br /> <input type="text" class="form-control" data-property="topicsPerPage"><br />
|
||||
<strong>[[user:posts_per_page]]</strong><br /> <input type="text" class="form-control" data-property="postsPerPage"><br />
|
||||
</div>
|
||||
|
||||
<!-- BEGIN settings -->
|
||||
<h4>{settings.title}</h4>
|
||||
<div class="well">
|
||||
{settings.content}
|
||||
</div>
|
||||
<!-- END settings -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a id="submitBtn" href="#" class="btn btn-primary">[[global:save_changes]]</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
File diff suppressed because one or more lines are too long
@@ -1,52 +0,0 @@
|
||||
|
||||
|
||||
<!-- IF redis -->
|
||||
<h1><i class="fa fa-hdd-o"></i> Redis</h1>
|
||||
<hr />
|
||||
<div id="admin-redis-info">
|
||||
<span>Redis Version</span> <span class="text-right">{redis_version}</span><br/>
|
||||
<hr/>
|
||||
<span>Uptime in Seconds</span> <span class="text-right">{uptime_in_seconds}</span><br/>
|
||||
<span>Uptime in Days</span> <span class="text-right">{uptime_in_days}</span><br/>
|
||||
<hr/>
|
||||
<span>Connected Clients</span> <span class="text-right">{connected_clients}</span><br/>
|
||||
<span>Connected Slaves</span> <span class="text-right">{connected_slaves}</span><br/>
|
||||
<span>Blocked Clients</span> <span class="text-right">{blocked_clients}</span><br/>
|
||||
<hr/>
|
||||
|
||||
<span>Used Memory</span> <span class="text-right">{used_memory_human}</span><br/>
|
||||
<span>Memory Fragmentation Ratio</span> <span class="text-right">{mem_fragmentation_ratio}</span><br/>
|
||||
<hr/>
|
||||
<span>Total Connections Received</span> <span class="text-right">{total_connections_received}</span><br/>
|
||||
<span>Total Commands Processed</span> <span class="text-right">{total_commands_processed}</span><br/>
|
||||
<span>Instantaneous Ops. Per Second</span> <span class="text-right">{instantaneous_ops_per_sec}</span><br/>
|
||||
<hr/>
|
||||
<span>Keyspace Hits</span> <span class="text-right">{keyspace_hits}</span><br/>
|
||||
<span>Keyspace Misses</span> <span class="text-right">{keyspace_misses}</span><br/>
|
||||
</div>
|
||||
<hr />
|
||||
<h3>Raw Info </h3>
|
||||
<div class="highlight">
|
||||
<pre>{raw}</pre>
|
||||
</div>
|
||||
<!-- ENDIF redis -->
|
||||
|
||||
<!-- IF mongo -->
|
||||
<h1><i class="fa fa-hdd-o"></i> Mongo</h1>
|
||||
<hr />
|
||||
<div id="admin-redis-info">
|
||||
|
||||
<span>Collections</span> <span class="text-right">{collections}</span><br/>
|
||||
<span>Objects</span> <span class="text-right">{objects}</span><br/>
|
||||
<span>Avg. Object Size</span> <span class="text-right">{avgObjSize} kb</span><br/>
|
||||
<hr/>
|
||||
<span>Data Size</span> <span class="text-right">{dataSize} kb</span><br/>
|
||||
<span>Storage Size</span> <span class="text-right">{storageSize} kb</span><br/>
|
||||
<span>File Size</span> <span class="text-right">{fileSize} kb</span><br/>
|
||||
</div>
|
||||
<hr />
|
||||
<h3>Raw Info </h3>
|
||||
<div class="highlight">
|
||||
<pre>{raw}</pre>
|
||||
</div>
|
||||
<!-- ENDIF mongo -->
|
||||
@@ -1,5 +0,0 @@
|
||||
<h1><i class="fa fa-calendar-o"></i> Events</h1>
|
||||
|
||||
<pre>
|
||||
{eventdata}
|
||||
</pre>
|
||||
@@ -1,52 +0,0 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-picture-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="Upload Picture" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="myModalLabel">Upload Picture</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="uploadForm" action="" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="userPhoto">Upload a picture</label>
|
||||
<input type="file" id="userPhotoInput" name="userPhoto">
|
||||
<p class="help-block"></p>
|
||||
</div>
|
||||
<input id="imageUploadCsrf" type="hidden" name="_csrf" value="" />
|
||||
<input type="hidden" id="params" name="params">
|
||||
</form>
|
||||
|
||||
<div id="upload-progress-box" class="progress progress-striped">
|
||||
<div id="upload-progress-bar" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0" aria-valuemin="0">
|
||||
<span class="sr-only"> success</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="alert-status" class="alert alert-info hide"></div>
|
||||
<div id="alert-success" class="alert alert-success hide"></div>
|
||||
<div id="alert-error" class="alert alert-danger hide"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
<button id="pictureUploadSubmitBtn" class="btn btn-primary">Upload Picture</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<div id="alert_window"></div>
|
||||
|
||||
<div id="footer" class="container" style="padding-top: 50px; display:none;">
|
||||
<footer class="footer">Copyright © 2014 <a target="_blank" href="http://www.nodebb.com">NodeBB</a> by <a target="_blank" href="https://github.com/psychobunny">psychobunny</a>, <a href="https://github.com/julianlam" target="_blank">julianlam</a>, <a href="https://github.com/barisusakli" target="_blank">barisusakli</a> from <a target="_blank" href="http://www.designcreateplay.com">designcreateplay</a></footer>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
require(['forum/admin/footer']);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,99 +0,0 @@
|
||||
<h1><i class="fa fa-group"></i> Groups</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="groups">
|
||||
<ul id="groups-list">
|
||||
<!-- BEGIN groups -->
|
||||
<li data-gid="{groups.gid}">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<h2>{groups.name}</h2>
|
||||
<p>{groups.description}</p>
|
||||
<!-- IF groups.deletable -->
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" data-action="members">Members</button>
|
||||
<button class="btn btn-danger" data-action="delete">Delete Group</button>
|
||||
</div>
|
||||
<!-- ENDIF groups.deletable -->
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<ul class="pull-right members">
|
||||
<!-- BEGIN members --><li data-uid="{groups.members.uid}" title="{groups.members.username}"><img src="{groups.members.picture}" /></li><!-- END members -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END groups -->
|
||||
</ul>
|
||||
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary" id="create">New Group</button>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="create-modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Create Group</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger hide" id="create-modal-error"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="group-name">Group Name</label>
|
||||
<input type="text" class="form-control" id="create-group-name" placeholder="Group Name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="group-name">Description</label>
|
||||
<input type="text" class="form-control" id="create-group-desc" placeholder="A short description about your group" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="create-modal-go">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="group-details-modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Manage Group</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger hide" id="create-modal-error"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="group-name">Group Name</label>
|
||||
<input type="text" class="form-control" id="change-group-name" placeholder="Group Name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="group-name">Description</label>
|
||||
<input type="text" class="form-control" id="change-group-desc" placeholder="A short description about your group" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Members</label>
|
||||
<p>Click on a user to remove them from the group</p>
|
||||
<ul class="members current_members" id="group-details-members"></ul>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="add-member">Add User to Group</label>
|
||||
<input type="text" class="form-control" id="group-details-search" placeholder="Search Users" />
|
||||
<ul class="members" id="group-details-search-results"></ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
@@ -1,155 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>NodeBB Administration Panel</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{relative_path}/vendor/colorpicker/colorpicker.css">
|
||||
<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />
|
||||
|
||||
<script>
|
||||
var RELATIVE_PATH = "{relative_path}";
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/es5-shim/2.3.0/es5-shim.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js"></script>
|
||||
<script>__lt_ie_9__ = 1;</script>
|
||||
<![endif]-->
|
||||
|
||||
<script src="{relative_path}/vendor/jquery/js/jquery.js"></script>
|
||||
<script src="{relative_path}/vendor/jquery/js/jquery-ui-1.10.4.custom.js"></script>
|
||||
<script src="{relative_path}/vendor/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="{relative_path}/socket.io/socket.io.js"></script>
|
||||
<script src="{relative_path}/src/app.js?{cache-buster}"></script>
|
||||
<script src="{relative_path}/src/templates.js?{cache-buster}"></script>
|
||||
<script src="{relative_path}/src/translator.js?{cache-buster}"></script>
|
||||
<script src="{relative_path}/src/ajaxify.js?{cache-buster}"></script>
|
||||
<script src="{relative_path}/vendor/jquery/timeago/jquery.timeago.js"></script>
|
||||
<script src="{relative_path}/vendor/jquery/js/jquery.form.js"></script>
|
||||
<script src="{relative_path}/vendor/requirejs/require.js"></script>
|
||||
<script src="{relative_path}/vendor/bootbox/bootbox.min.js"></script>
|
||||
<script src="{relative_path}/vendor/colorpicker/colorpicker.js"></script>
|
||||
<script src="{relative_path}/vendor/xregexp/xregexp.js"></script>
|
||||
<script src="{relative_path}/vendor/xregexp/unicode/unicode-base.js"></script>
|
||||
<script src="{relative_path}/vendor/tabIndent/tabIndent.js"></script>
|
||||
|
||||
<script>
|
||||
require.config({
|
||||
baseUrl: "{relative_path}/src/modules",
|
||||
waitSeconds: 3,
|
||||
urlArgs: "{cache-buster}",
|
||||
paths: {
|
||||
"forum": '../forum'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="{relative_path}/src/overrides.js"></script>
|
||||
<script src="{relative_path}/src/utils.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="admin">
|
||||
<div class="navbar navbar-inverse navbar-fixed-top header">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{relative_path}/admin/index">NodeBB ACP</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<a href="{relative_path}/admin/index"><i class="fa fa-home" title="Home"></i><span class="visible-xs-inline"> Home</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{relative_path}/admin/settings"><i class="fa fa-cogs" title="Settings"></i><span class="visible-xs-inline"> Settings</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{relative_path}/" target="_top"><i class="fa fa-book" title="Forum"></i><span class="visible-xs-inline"> Forum</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="reconnect"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul id="logged-in-menu" class="nav navbar-nav navbar-right">
|
||||
<li id="user_label" class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="user_dropdown">
|
||||
<img src="{userpicture}"/>
|
||||
</a>
|
||||
<ul id="user-control-list" class="dropdown-menu" aria-labelledby="user_dropdown">
|
||||
<li>
|
||||
<a id="user-profile-link" href="{relative_path}/user/{userslug}" target="_top"><span>Profile</span></a>
|
||||
</li>
|
||||
<li id="logout-link">
|
||||
<a href="#">Log out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input id="csrf_token" type="hidden" template-variable="csrf" value="{csrf}" />
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<div class="well sidebar-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">NodeBB</li>
|
||||
<li class="active"><a href="{relative_path}/admin/index"><i class="fa fa-fw fa-home"></i> Home</a></li>
|
||||
<li><a href="{relative_path}/admin/categories/active"><i class="fa fa-fw fa-folder"></i> Categories</a></li>
|
||||
<li><a href="{relative_path}/admin/users/latest"><i class="fa fa-fw fa-user"></i> Users</a></li>
|
||||
<li><a href="{relative_path}/admin/groups"><i class="fa fa-fw fa-group"></i> Groups</a></li>
|
||||
<li><a href="{relative_path}/admin/topics"><i class="fa fa-fw fa-book"></i> Topics</a></li>
|
||||
<li><a href="{relative_path}/admin/themes"><i class="fa fa-fw fa-th"></i> Themes</a></li>
|
||||
<li><a href="{relative_path}/admin/plugins"><i class="fa fa-fw fa-code-fork"></i> Plugins</a></li>
|
||||
<li><a href="{relative_path}/admin/languages"><i class="fa fa-fw fa-comments-o"></i> Languages</a></li>
|
||||
<li><a href="{relative_path}/admin/settings"><i class="fa fa-fw fa-cogs"></i> Settings</a></li>
|
||||
<li><a href="{relative_path}/admin/database"><i class="fa fa-fw fa-hdd-o"></i> Database</a></li>
|
||||
<li><a href="{relative_path}/admin/events"><i class="fa fa-fw fa-calendar-o"></i> Events</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- IF authentication.length -->
|
||||
<div class="well sidebar-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">Social Authentication</li>
|
||||
<!-- BEGIN authentication -->
|
||||
<li>
|
||||
<a href="{relative_path}/admin{authentication.route}"><i class="fa fa-fw {authentication.icon}"></i> {authentication.name}</a>
|
||||
</li>
|
||||
<!-- END authentication -->
|
||||
</ul>
|
||||
</div>
|
||||
<!-- ENDIF authentication.length -->
|
||||
<div class="well sidebar-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">Plugins</li>
|
||||
<!-- BEGIN plugins -->
|
||||
<li>
|
||||
<a href="{relative_path}/admin{plugins.route}"><i class="fa fa-fw {plugins.icon}"></i> {plugins.name}</a>
|
||||
</li>
|
||||
<!-- END plugins -->
|
||||
</ul>
|
||||
</div>
|
||||
<!-- IF env -->
|
||||
<div class="well sidebar-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">Development</li>
|
||||
<li><a href="{relative_path}/admin/logger"><i class="fa fa-fw fa-th"></i> Logger</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- ENDIF env -->
|
||||
</div><!--/span-->
|
||||
|
||||
<div class="col-sm-9" id="content">
|
||||
@@ -1,79 +0,0 @@
|
||||
<div class="home">
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Welcome to NodeBB</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a target="_blank" href="http://community.nodebb.org" class="btn btn-default btn-sm"><i class="fa fa-comment"></i> NodeBB Community Forum</a>
|
||||
<a target="_blank" href="http://community.nodebb.org/" class="btn btn-default btn-sm"><i class="fa fa-github-alt"></i> Get Plugins and Themes</a>
|
||||
<a target="_blank" href="http://www.twitter.com/NodeBB" class="btn btn-default btn-sm"><i class="fa fa-twitter"></i> Follow @NodeBB</a>
|
||||
<a target="_blank" href="https://github.com/designcreateplay/NodeBB/wiki" class="btn btn-default btn-sm"><i class="fa fa-question-circle"></i> NodeBB Wiki</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Notices</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<!-- IF emailerInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF emailerInstalled --> Emailer Installed
|
||||
</div>
|
||||
<div>
|
||||
<!-- IF searchInstalled --><i class="fa fa-check alert-success"></i><!-- ELSE --><i class="fa fa-times alert-danger"></i><!-- ENDIF searchInstalled --> Search Plugin Installed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Unique Visitors</div>
|
||||
<div class="panel-body">
|
||||
<div id="unique-visitors">
|
||||
<div class="text-center pull-left">
|
||||
<div id="day"></div>
|
||||
<div>Day</div>
|
||||
</div>
|
||||
<div class="text-center pull-left">
|
||||
<div id="week"></div>
|
||||
<div>Week</div>
|
||||
</div>
|
||||
<div class="text-center pull-left">
|
||||
<div id="month"></div>
|
||||
<div>Month</div>
|
||||
</div>
|
||||
<div class="text-center pull-left">
|
||||
<div id="alltime"></div>
|
||||
<div>All Time</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-6 pull-right">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Updates</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-info version-check">
|
||||
<p>You are running <strong>NodeBB v<span id="version">{version}</span></strong>.</p>
|
||||
</div>
|
||||
<p>
|
||||
Always make sure that your <strong>NodeBB</strong> is up to date for the latest security patches and bug fixes.
|
||||
</p>
|
||||
<p class="pull-right">
|
||||
<button class="btn btn-warning restart">Restart NodeBB</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Active Users <small><span class="badge" id="connections"></span> socket connections</small></div>
|
||||
<div class="panel-body">
|
||||
<div id="active_users"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
<div class="languages">
|
||||
<h1><i class="fa fa-comments-o"></i> Languages</h1>
|
||||
<hr />
|
||||
<p>
|
||||
The following setting(s) determine the language settings for your NodeBB.
|
||||
The default language determines the language settings for all users who
|
||||
are visiting your NodeBB. <!-- Keep in mind that individual users may decide
|
||||
to switch languages for their own accounts. -->
|
||||
</p>
|
||||
|
||||
<form class="row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="defaultLang">Default Language</label>
|
||||
<select data-field="defaultLang" class="form-control">
|
||||
<!-- BEGIN languages -->
|
||||
<option value="{languages.code}">{languages.name} ({languages.code})</option>
|
||||
<!-- END languages -->
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" id="save">Save</button>
|
||||
@@ -1,46 +0,0 @@
|
||||
<h1><i class="fa fa-th"></i> Logger</h1>
|
||||
<hr />
|
||||
|
||||
<h3>Logger Settings</h3>
|
||||
<div class="alert alert-warning">
|
||||
|
||||
<p>
|
||||
By enabling the check boxes, you will receive logs to your terminal. If you specify a path, logs will then be saved to a file instead. HTTP logging is useful for collecting statistics about who, when, and what people access on your forum. In addition to logging HTTP requests, we can also log socket.io events. Socket.io logging, in combination with redis-cli monitor, can be very helpful for learning NodeBB's internals.
|
||||
</p>
|
||||
<br/>
|
||||
<p>
|
||||
Simply check/uncheck the logging settings to enable or disable logging on the fly. No restart needed.
|
||||
</p>
|
||||
<br/>
|
||||
|
||||
<form>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" data-field="loggerStatus"> <strong>Enable HTTP logging</strong>
|
||||
</label>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" data-field="loggerIOStatus"> <strong>Enable socket.io event logging</strong>
|
||||
</label>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
<label>Path to log file</label>
|
||||
<input class="form-control" type="text" placeholder="/path/to/log/file.log ::: leave blank to log to your terminal" data-field="loggerPath" />
|
||||
<br />
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" id="save">Save</button>
|
||||
|
||||
<script>
|
||||
require(['forum/admin/settings'], function(Settings) {
|
||||
Settings.prepare();
|
||||
});
|
||||
</script>
|
||||
@@ -1,25 +0,0 @@
|
||||
<h1><i class="fa fa-code-fork"></i> Plugins</h1>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
<strong>Interested in writing plugins for NodeBB?</strong>
|
||||
</p>
|
||||
<p>
|
||||
Full documentation regarding plugin authoring can be found in the <a target="_blank" href="https://github.com/designcreateplay/NodeBB/wiki/Writing-Plugins-for-NodeBB">NodeBB Wiki</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="plugins">
|
||||
<!-- BEGIN plugins -->
|
||||
<li data-plugin-id="{plugins.id}">
|
||||
<h2><strong>{plugins.name}</strong></h2>
|
||||
<div class="pull-right">
|
||||
<button data-action="toggleActive" class="btn <!-- IF plugins.active -->btn-warning<!-- ELSE -->btn-success<!-- ENDIF plugins.active -->">{plugins.activeText}</button>
|
||||
</div>
|
||||
<p>{plugins.description}</p>
|
||||
<p>For more information: <a href="{plugins.url}">{plugins.url}</a></p>
|
||||
</li>
|
||||
<!-- END plugins -->
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
<h1><i class="fa fa-cogs"></i> Settings</h1>
|
||||
<hr />
|
||||
|
||||
<ul id="settings-tab" class="nav nav-tabs">
|
||||
<li class="active"><a href="#general" data-toggle="tab">General</a></li>
|
||||
<li><a href="#privilege-thresholds" data-toggle="tab">Privilege Thresholds</a></li>
|
||||
<li><a href="#email" data-toggle="tab">Email</a></li>
|
||||
<li><a href="#user" data-toggle="tab">User</a></li>
|
||||
<li><a href="#post" data-toggle="tab">Post</a></li>
|
||||
<li><a href="#pagination" data-toggle="tab">Pagination</a></li>
|
||||
<li><a href="#web-crawler" data-toggle="tab">Web Crawler</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="general">
|
||||
<div class="alert alert-warning">
|
||||
<form>
|
||||
<label>Site Title</label>
|
||||
<input class="form-control" type="text" placeholder="Your Community Name" data-field="title" /><br />
|
||||
<label>Browser Title</label>
|
||||
<input class="form-control" type="text" placeholder="Browser Title" data-field="browserTitle" /><br />
|
||||
<label>Site Description</label>
|
||||
<input type="text" class="form-control" placeholder="A short description about your community" data-field="description" /><br />
|
||||
<label>Site Keywords</label>
|
||||
<input type="text" class="form-control" placeholder="Keywords describing your community, comma-seperated" data-field="keywords" /><br />
|
||||
<label>Site Logo</label>
|
||||
<input id="logoUrl" type="text" class="form-control" placeholder="Path to a logo to display on forum header" data-field="brand:logo" /><br />
|
||||
<input id="uploadLogoBtn" type="button" class="btn btn-default" value="Upload Logo"></input> <br /> <br/>
|
||||
<label>Favicon</label><br />
|
||||
<input id="faviconUrl" type="text" class="form-control" placeholder="favicon.ico" data-field="brand:favicon" /><br />
|
||||
<input id="uploadFaviconBtn" type="button" class="btn btn-default" value="Upload Favicon"></input> <br />
|
||||
<hr/>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="allowGuestSearching"> <strong>Allow guests to search without logging in</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="allowTopicsThumbnail"> <strong>Allow users to upload topic thumbnails</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="useOutgoingLinksPage"> <strong>Use Outgoing Links Warning Page</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="disableSocialButtons"> <strong>Disable social buttons</strong>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="privilege-thresholds">
|
||||
<form>
|
||||
<div class="alert alert-warning">
|
||||
<p>Use <strong>privilege thresholds</strong> to manage how much reputation a user must gain to receive moderator access.</p><br />
|
||||
<strong>Manage Thread</strong><br /> <input type="text" class="form-control" value="1000" data-field="privileges:manage_topic"><br />
|
||||
<strong>Manage Content</strong><br /> <input type="text" class="form-control" value="1000" data-field="privileges:manage_content"><br />
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="privileges:disabled"> <strong>Disable Privilege Threshold System</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="email">
|
||||
<form>
|
||||
<div class="alert alert-warning">
|
||||
<div>
|
||||
<p>
|
||||
Please ensure that you have installed a third-party emailer (e.g. PostageApp, Mailgun, Mandrill, SendGrid, etc), otherwise emails will not be sent by NodeBB
|
||||
</p>
|
||||
<p>
|
||||
<strong>Email Address</strong><br />
|
||||
The following email address refers to the email that the recipient will see in the "From" and "Reply To" fields.
|
||||
</p>
|
||||
<input type="text" class="form-control input-lg" data-field="email:from" placeholder="info@example.org" /><br />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="user">
|
||||
<form>
|
||||
<div class="alert alert-warning">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="allowRegistration" checked> <strong>Allow registration</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="privateUserInfo"> <strong>Make user info private</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="disableSignatures"> <strong>Disable signatures</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="profile:convertProfileImageToPNG"> <strong>Convert profile image uploads to PNG</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Days to remember user login sessions</label>
|
||||
<input type="text" class="form-control" data-field="loginDays" placeholder="14" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Maximum User Image File Size</label>
|
||||
<input type="text" class="form-control" placeholder="Maximum size of uploaded user images in kilobytes" data-field="maximumProfileImageSize" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Username Length</label>
|
||||
<input type="text" class="form-control" value="2" data-field="minimumUsernameLength">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Maximum Username Length</label>
|
||||
<input type="text" class="form-control" value="16" data-field="maximumUsernameLength">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Minimum Password Length</label>
|
||||
<input type="text" class="form-control" value="6" data-field="minimumPasswordLength">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Maximum Signature Length</label>
|
||||
<input type="text" class="form-control" value="255" data-field="maximumSignatureLength">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Forum Terms of Use <small>(Leave blank to disable)</small></label>
|
||||
<textarea class="form-control" data-field="termsOfUse"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="post">
|
||||
<form>
|
||||
<div class="alert alert-warning">
|
||||
<strong>Post Delay</strong><br /> <input type="text" class="form-control" value="10000" data-field="postDelay"><br />
|
||||
<strong>Minimum Title Length</strong><br /> <input type="text" class="form-control" value="3" data-field="minimumTitleLength"><br />
|
||||
<strong>Maximum Title Length</strong><br /> <input type="text" class="form-control" value="255" data-field="maximumTitleLength"><br />
|
||||
<strong>Minimum Post Length</strong><br /> <input type="text" class="form-control" value="8" data-field="minimumPostLength"><br />
|
||||
<strong>Chat Messages To Display</strong><br /> <input type="text" class="form-control" value="50" data-field="chatMessagesToDisplay"><br />
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="allowGuestPosting"> <strong>Allow guests to post without logging in</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="allowFileUploads"> <strong>Allow users to upload regular files</strong>
|
||||
</label>
|
||||
</div>
|
||||
<strong>Maximum File Size</strong><br /> <input type="text" class="form-control" value="2048" data-field="maximumFileSize"><br />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="pagination">
|
||||
<form>
|
||||
<div class="alert alert-warning">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-field="usePagination"> <strong>Paginate topics and posts instead of using infinite scroll.</strong>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<strong>Topics per Page</strong><br /> <input type="text" class="form-control" value="20" data-field="topicsPerPage"><br />
|
||||
<strong>Posts per Page</strong><br /> <input type="text" class="form-control" value="20" data-field="postsPerPage"><br />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="web-crawler">
|
||||
<form>
|
||||
<div class="alert alert-warning">
|
||||
<strong>Custom Robots.txt <small>Leave blank for default</small></strong><br />
|
||||
<textarea class="form-control" data-field="robots.txt"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="btn btn-primary" id="save">Save</button>
|
||||
|
||||
<script>
|
||||
require(['forum/admin/settings'], function(Settings) {
|
||||
Settings.prepare();
|
||||
});
|
||||
</script>
|
||||
@@ -1,144 +0,0 @@
|
||||
<h1><i class="fa fa-th"></i> Themes</h1>
|
||||
<hr />
|
||||
|
||||
<div class="themes">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#" data-target="#themes" data-toggle="tab">Themes</a></li>
|
||||
<li><a href="#" data-target="#customise" data-toggle="tab">Customise</a></li>
|
||||
<li><a href="#" data-target="#widgets" data-toggle="tab">Widgets</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="themes">
|
||||
<h3>Revert to Default</h3>
|
||||
<p class="alert">
|
||||
<button class="btn btn-warning" id="revert_theme">Revert</button> This will remove any custom theme applied to your NodeBB, and restore the base theme.
|
||||
</p>
|
||||
|
||||
<h3>Installed Themes</h3>
|
||||
<p>
|
||||
The following themes are currently installed in this NodeBB instance.
|
||||
</p>
|
||||
<ul class="directory" id="installed_themes">
|
||||
<li><i class="fa fa-refresh fa-spin"></i> Checking for installed themes...</li>
|
||||
</ul>
|
||||
|
||||
<h3>Bootswatch Themes</h3>
|
||||
<p>
|
||||
NodeBB Themes are powered by Bootswatch, a repository containing themes built
|
||||
with Bootstrap as a base theme.
|
||||
</p>
|
||||
<ul class="directory" id="bootstrap_themes">
|
||||
<li><i class="fa fa-refresh fa-spin"></i> Loading Themes</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="tab-pane" id="customise">
|
||||
<h3>Custom CSS</h3>
|
||||
<p>
|
||||
You may also opt to enter your own CSS declarations here, which will be applied after all other styles.
|
||||
</p>
|
||||
<textarea class="well" data-field="customCSS" placeholder="Enter your custom CSS here..."></textarea>
|
||||
|
||||
<form class="form">
|
||||
<div class="form-group">
|
||||
<label for="useCustomCSS">
|
||||
Use Custom CSS?
|
||||
<input id="useCustomCSS" type="checkbox" data-field="useCustomCSS" />
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button class="btn btn-primary" id="save">Save</button>
|
||||
</div>
|
||||
<div class="tab-pane" id="widgets">
|
||||
<h3>Widgets</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 pull-right">
|
||||
<!-- BEGIN areas -->
|
||||
<div class="area">
|
||||
<h4>{areas.name} <small>{areas.template} / {areas.location}</small> <button data-template="{areas.template}" data-location="{areas.location}" class="btn btn-success btn-xs pull-right">Save</button></h4>
|
||||
<div class="well widget-area">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- END areas -->
|
||||
</div>
|
||||
<div class="col-xs-6 pull-left">
|
||||
<div class="available-widgets">
|
||||
<h4>Available Widgets <small>Drag and drop widgets into templates</small></h4>
|
||||
<div>
|
||||
<!-- BEGIN widgets -->
|
||||
<div data-widget="{widgets.widget}" class="panel panel-default pointer">
|
||||
<div class="panel-heading">
|
||||
<strong>{widgets.name}</strong> <small>{widgets.description}</small>
|
||||
</div>
|
||||
<div class="panel-body hidden">
|
||||
<form>
|
||||
{widgets.content}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END widgets -->
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="available-containers">
|
||||
<h4>Available Containers <small>Drag and drop on top of any widget</small></h4>
|
||||
<div class="containers">
|
||||
<div class="pointer" style="padding: 20px; border: 1px dotted #dedede; margin-bottom: 20px;" data-container-html=" ">
|
||||
None
|
||||
</div>
|
||||
<div class="well pointer" data-container-html='<div class="well">{body}</div>'>
|
||||
Well
|
||||
</div>
|
||||
<div class="jumbotron pointer" data-container-html='<div class="jumbotron">{body}</div>'>
|
||||
Jumbotron
|
||||
</div>
|
||||
<div class="panel" data-container-html='<div class="panel"><div class="panel-body">{body}</div></div>'>
|
||||
<div class="panel-body pointer">
|
||||
Panel
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default pointer" data-container-html='<div class="panel panel-default"><div class="panel-heading">{title}</div><div class="panel-body">{body}</div></div>'>
|
||||
<div class="panel-heading">
|
||||
Panel Header
|
||||
<div class="pull-right color-selector">
|
||||
<button data-class="panel-default" class="btn btn-xs"> </button>
|
||||
<button data-class="panel-primary" class="btn btn-xs btn-primary"> </button>
|
||||
<button data-class="panel-success" class="btn btn-xs btn-success"> </button>
|
||||
<button data-class="panel-info" class="btn btn-xs btn-info"> </button>
|
||||
<button data-class="panel-warning" class="btn btn-xs btn-warning"> </button>
|
||||
<button data-class="panel-danger" class="btn btn-xs btn-danger"> </button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Panel Body
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info pointer" data-container-html='<div class="alert alert-info">{body}</div>'>
|
||||
Alert
|
||||
<div class="pull-right color-selector">
|
||||
<button data-class="alert-success" class="btn btn-xs btn-success"> </button>
|
||||
<button data-class="alert-info" class="btn btn-xs btn-info"> </button>
|
||||
<button data-class="alert-warning" class="btn btn-xs btn-warning"> </button>
|
||||
<button data-class="alert-danger" class="btn btn-xs btn-danger"> </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var bootswatchListener = function(data) {
|
||||
require(['forum/admin/themes'], function(t) {
|
||||
t.render(data);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -1,33 +0,0 @@
|
||||
<h1><i class="fa fa-book"></i> Topics</h1>
|
||||
<hr />
|
||||
|
||||
|
||||
<ul class="topics">
|
||||
<!-- BEGIN topics -->
|
||||
<li data-tid="{topics.tid}" data-locked="{topics.locked}" data-pinned="{topics.pinned}" data-deleted="{topics.deleted}">
|
||||
<div class="btn-group pull-right">
|
||||
<button data-action="pin" class="btn"><i class="fa fa-thumb-tack"></i></button>
|
||||
<button data-action="lock" class="btn"><i class="fa fa-lock"></i></button>
|
||||
<button data-action="delete" class="btn"><i class="fa fa-trash-o"></i></button>
|
||||
</div>
|
||||
<a target="_blank" href="{relative_path}/topic/{topics.slug}">{topics.title}</a>
|
||||
<ul>
|
||||
<li><i class="fa fa-clock-o"></i> Posted <span class="timeago" title="{topics.relativeTime}"></span> by {topics.username}</li>
|
||||
<li><i class="fa fa-comments"></i> {topics.postcount} post(s)</li>
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
</li>
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
|
||||
<!-- IF notopics -->
|
||||
<div class="alert alert-warning" id="category-no-topics">
|
||||
<strong>There are no topics.</strong>
|
||||
</div>
|
||||
<!-- ELSE -->
|
||||
<div class="text-center">
|
||||
<button id="topics_loadmore" class="btn btn-primary">Load More Topics</button>
|
||||
</div>
|
||||
<!-- ENDIF notopics -->
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<h1><i class="fa fa-user"></i> Users</h1>
|
||||
|
||||
<button id="createUser" class="btn btn-primary">Create User</button>
|
||||
<a target="_blank" href="/admin/users/csv" class="btn btn-primary">Download CSV</a>
|
||||
<hr />
|
||||
<ul class="nav nav-pills">
|
||||
<li class='active'><a href='{relative_path}/admin/users/latest'>Latest Users</a></li>
|
||||
<li class=''><a href='{relative_path}/admin/users/sort-posts'>Top Posters</a></li>
|
||||
<li class=''><a href='{relative_path}/admin/users/sort-reputation'>Most Reputation</a></li>
|
||||
<li class=''><a href='{relative_path}/admin/users/search'>Search</a></li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
<div class="search {search_display} well">
|
||||
<input class="form-control" id="search-user" type="text" placeholder="Enter a username to search"/><br />
|
||||
<i class="fa fa-spinner fa-spin none"></i>
|
||||
<span id="user-notfound-notify" class="label label-danger hide">User not found!</span><br/>
|
||||
</div>
|
||||
|
||||
<ul id="users-container" class="users admin">
|
||||
<!-- BEGIN users -->
|
||||
<div class="users-box" data-uid="{users.uid}" data-admin="{users.administrator}" data-username="{users.username}" data-banned="{users.banned}">
|
||||
<a href="{relative_path}/user/{users.userslug}"><img src="{users.picture}" class="img-thumbnail"/></a>
|
||||
<br/>
|
||||
<a href="{relative_path}/user/{users.userslug}">{users.username}</a>
|
||||
<br/>
|
||||
<div title="reputation">
|
||||
<i class='fa fa-star'></i>
|
||||
<span id='reputation'>{users.reputation}</span>
|
||||
</div>
|
||||
<div title="post count">
|
||||
<i class='fa fa-pencil'></i>
|
||||
<span id='postcount'>{users.postcount}</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" class="btn btn-default admin-btn">Make Admin</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#" class="btn btn-default ban-btn">Ban</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END users -->
|
||||
</ul>
|
||||
|
||||
<div class="modal fade" id="create-modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Create User</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger hide" id="create-modal-error"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="group-name">User Name</label>
|
||||
<input type="text" class="form-control" id="create-user-name" placeholder="User Name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="group-name">Email</label>
|
||||
<input type="text" class="form-control" id="create-user-email" placeholder="Email of this user" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="group-name">Password</label>
|
||||
<input type="password" class="form-control" id="create-user-password" placeholder="Password" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="group-name">Password Confirm</label>
|
||||
<input type="password" class="form-control" id="create-user-password-again" placeholder="Password" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="create-modal-go">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="text-center {loadmore_display}">
|
||||
<button id="load-more-users-btn" class="btn btn-primary">Load More</button>
|
||||
</div>
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
@@ -1,117 +0,0 @@
|
||||
|
||||
<input type="hidden" template-variable="category_id" value="{cid}" />
|
||||
<input type="hidden" template-variable="category_name" value="{name}" />
|
||||
<input type="hidden" template-variable="currentPage" value="{currentPage}" />
|
||||
<input type="hidden" template-variable="pageCount" value="{pageCount}" />
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">{name} <a target="_blank" href="../{cid}.rss"><i class="fa fa-rss-square"></i></a></span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div>
|
||||
<!-- IF privileges.write -->
|
||||
<button id="new_post" class="btn btn-primary">[[category:new_topic_button]]</button>
|
||||
<!-- ENDIF privileges.write -->
|
||||
<!-- IF !config.disableSocialButtons -->
|
||||
<div class="inline-block pull-right">
|
||||
<a href="#" id="facebook-share"><i class="fa fa-facebook-square fa-2x"></i></a>
|
||||
<a href="#" id="twitter-share"><i class="fa fa-twitter-square fa-2x"></i></a>
|
||||
<a href="#" id="google-share"><i class="fa fa-google-plus-square fa-2x"></i></a>
|
||||
</div>
|
||||
<!-- ENDIF !config.disableSocialButtons -->
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<!-- IF !topics.length -->
|
||||
<div class="alert alert-warning" id="category-no-topics">
|
||||
[[category:no_topics]]
|
||||
</div>
|
||||
<!-- ENDIF !topics.length -->
|
||||
|
||||
<div class="category row">
|
||||
<div class="{topic_row_size}" no-widget-class="col-lg-12 col-sm-12">
|
||||
<ul id="topics-container" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}">
|
||||
<meta itemprop="itemListOrder" content="descending">
|
||||
<!-- BEGIN topics -->
|
||||
<li class="category-item <!-- IF topics.deleted -->deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread -->unread<!-- ENDIF topics.unread -->" itemprop="itemListElement" data-tid="{topics.tid}" data-index="{topics.index}">
|
||||
|
||||
<div class="col-md-12 col-xs-12 panel panel-default topic-row">
|
||||
|
||||
<!--
|
||||
todo: tidy this up, not sure what to do with the topic thumbs
|
||||
todo: fix this nesting if issue#1065 is a win
|
||||
todo: add a check for config.allowTopicsThumbnail if issue#1066 is a win
|
||||
-->
|
||||
<a href="../../user/{topics.user.userslug}" class="pull-left">
|
||||
<img src="<!-- IF topics.thumb -->{topics.thumb}<!-- ELSE -->{topics.user.picture}<!-- ENDIF topics.thumb -->" class="img-rounded user-img" title="{topics.user.username}"/>
|
||||
</a>
|
||||
|
||||
<h3>
|
||||
<a href="../../topic/{topics.slug}" itemprop="url">
|
||||
<meta itemprop="name" content="{topics.title}">
|
||||
|
||||
<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
|
||||
<span class="topic-title">{topics.title}</span>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<small>
|
||||
<span class="topic-stats">
|
||||
[[category:posts]]
|
||||
<strong class="human-readable-number" title="{topics.postcount}">{topics.postcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span class="topic-stats">
|
||||
[[category:views]]
|
||||
<strong class="human-readable-number" title="{topics.viewcount}">{topics.viewcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span>
|
||||
[[category:posted]] <span class="timeago" title="{topics.relativeTime}"></span>
|
||||
</span>
|
||||
|
||||
<span class="pull-right">
|
||||
<!-- IF topics.unreplied -->
|
||||
[[category:no_replies]]
|
||||
<!-- ELSE -->
|
||||
<a href="../../user/{topics.teaser.userslug}">
|
||||
<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
|
||||
</a>
|
||||
<a href="../../topic/{topics.slug}#{topics.teaser.pid}">
|
||||
[[category:replied]]
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser.timestamp}"></span>
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
<!-- IF config.usePagination -->
|
||||
<div class="text-center">
|
||||
<ul class="pagination">
|
||||
<li class="previous pull-left"><a href="#"><i class="fa fa-chevron-left"></i> [[global:previouspage]]</a></li>
|
||||
<li class="next pull-right"><a href="#">[[global:nextpage]] <i class="fa fa-chevron-right"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- ENDIF config.usePagination -->
|
||||
</div>
|
||||
|
||||
<!-- IF topics.length -->
|
||||
<div widget-area="sidebar" class="col-md-3 col-xs-12 category-sidebar">
|
||||
<!-- BEGIN widgets -->
|
||||
{widgets.html}
|
||||
<!-- END widgets -->
|
||||
</div>
|
||||
<!-- ENDIF topics.length -->
|
||||
</div>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
<div id="chat-modal" class="chat-modal hide" tabindex="-1" role="dialog" aria-labelledby="Chat" aria-hidden="true" data-backdrop="none">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
||||
<h4>[[modules:chat.chatting_with]] <i id="chat-user-status" class="fa fa-circle status offline" title="[[global:offline]]"></i></h4>
|
||||
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul id="chat-content" class="well well-sm"></ul>
|
||||
<div class="input-group">
|
||||
<input id="chat-message-input" type="text" placeholder="[[modules:chat.placeholder]]" name="chat-message" class="form-control">
|
||||
<span class="input-group-btn">
|
||||
<button id="chat-message-send-btn" class="btn btn-primary" href="#" type="button">[[modules:chat.send]]</button>
|
||||
<button id="chat-close-btn" class="btn btn-warning" data-dismiss="modal" aria-hidden="true">[[global:close]]</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,91 +0,0 @@
|
||||
<div class="composer">
|
||||
|
||||
<div class="composer-container">
|
||||
<input class="title form-control" type="text" tabIndex="1" placeholder="[[topic:composer.title_placeholder]]" />
|
||||
|
||||
<!-- IF allowTopicsThumbnail -->
|
||||
<i class="fa fa-picture-o pull-right topic-thumb-btn topic-thumb-toggle-btn hide" title="[[topic:composer.thumb_title]]"></i>
|
||||
<div class="topic-thumb-container center-block hide">
|
||||
<form id="thumbForm" method="post" class="topic-thumb-form form-inline" enctype="multipart/form-data">
|
||||
<img class="topic-thumb-preview"></img>
|
||||
<div class="form-group">
|
||||
<label for="topic-thumb-url">[[topic:composer.thumb_url_label]]</label>
|
||||
<input type="text" id="topic-thumb-url" class="form-control" placeholder="[[topic:composer.thumb_url_placeholder]]" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="topic-thumb-file">[[topic:composer.thumb_file_label]]</label>
|
||||
<input type="file" id="topic-thumb-file" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group topic-thumb-ctrl">
|
||||
<i class="fa fa-spinner fa-spin hide topic-thumb-spinner" title="[[topic:composer.uploading]]"></i>
|
||||
<i class="fa fa-times topic-thumb-btn hide topic-thumb-clear-btn" title="[[topic:composer.thumb_remove]]"></i>
|
||||
<input id="thumbUploadCsrf" type="hidden" name="_csrf">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- ENDIF allowTopicsThumbnail -->
|
||||
|
||||
<div class="btn-toolbar formatting-bar">
|
||||
<div class="btn-group">
|
||||
<span class="btn btn-link" tabindex="-1"><i class="fa fa-bold"></i></span>
|
||||
<span class="btn btn-link" tabindex="-1"><i class="fa fa-italic"></i></span>
|
||||
<span class="btn btn-link" tabindex="-1"><i class="fa fa-list"></i></span>
|
||||
<span class="btn btn-link" tabindex="-1"><i class="fa fa-link"></i></span>
|
||||
|
||||
<!--[if gte IE 9]><!-->
|
||||
<span class="btn btn-link img-upload-btn hide" tabindex="-1">
|
||||
<i class="fa fa-picture-o"></i>
|
||||
</span>
|
||||
<span class="btn btn-link file-upload-btn hide" tabindex="-1">
|
||||
<i class="fa fa-upload"></i>
|
||||
</span>
|
||||
<!--<![endif]-->
|
||||
|
||||
<form id="fileForm" method="post" enctype="multipart/form-data">
|
||||
<input id="postUploadCsrf" type="hidden" name="_csrf">
|
||||
|
||||
<!--[if gte IE 9]><!-->
|
||||
<input type="file" id="files" name="files[]" multiple class="gte-ie9 hide"/>
|
||||
<!--<![endif]-->
|
||||
<!--[if lt IE 9]>
|
||||
<input type="file" id="files" name="files[]" class="lt-ie9 hide" value="Upload"/>
|
||||
<![endif]-->
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a data-pane=".tab-write" data-toggle="tab">[[topic:composer.write]]</a></li>
|
||||
<li><a data-pane=".tab-preview" data-toggle="tab">[[topic:composer.preview]]</a></li>
|
||||
<li class="hidden"><a data-pane=".tab-help" data-toggle="tab">[[topic:composer.help]]</a></li>
|
||||
<li class="btn-group pull-right action-bar">
|
||||
<button class="btn btn-default" data-action="discard" tabIndex="5"><i class="fa fa-times"></i> [[topic:composer.discard]]</button>
|
||||
<button data-action="post" class="btn btn-default btn-primary" tabIndex="3"><i class="fa fa-check"></i> [[topic:composer.submit]]</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active tab-write">
|
||||
<textarea class="write" tabIndex="2"></textarea>
|
||||
</div>
|
||||
<div class="tab-pane tab-preview">
|
||||
<div class="preview well"></div>
|
||||
</div>
|
||||
<div class="tab-pane tab-help">
|
||||
<div class="help well"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>
|
||||
|
||||
<div class="text-center instructions">
|
||||
<span>
|
||||
<span class="upload-instructions hide"><small>[[topic:composer.upload_instructions]]</small></span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="resizer"><div class="trigger text-center"><i class="fa fa-chevron-up"></i></div></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"custom_mapping": {
|
||||
"^admin/testing/categories.*": "admin/testing/categories",
|
||||
"^admin/topics.*": "admin/topics",
|
||||
"^admin/categories.*": "admin/categories",
|
||||
"^admin/users.*": "admin/users",
|
||||
"^admin/database.*": "admin/database",
|
||||
"^admin/events.*": "admin/events",
|
||||
"^admin/index.*": "admin/index",
|
||||
"^admin/themes.*": "admin/themes",
|
||||
"^admin/plugins/?$": "admin/plugins",
|
||||
"^admin/languages/?$": "admin/languages",
|
||||
"^admin/settings.*": "admin/settings",
|
||||
"^admin/logger.*": "admin/logger",
|
||||
"^admin/motd/?$": "admin/motd",
|
||||
"^admin/groups/?$": "admin/groups",
|
||||
"^users/sort-posts": "users",
|
||||
"^users/latest": "users",
|
||||
"^users/sort-reputation": "users",
|
||||
"^users/search": "users",
|
||||
"^user.*edit": "accountedit",
|
||||
"^user.*following": "following",
|
||||
"^user.*followers": "followers",
|
||||
"^user.*settings": "accountsettings",
|
||||
"^user.*favourites": "favourites",
|
||||
"^user.*posts": "accountposts",
|
||||
"^user/.*": "account",
|
||||
|
||||
"^recent/.*": "recent",
|
||||
|
||||
"reset/.*": "reset_code"
|
||||
},
|
||||
"force_refresh": {
|
||||
"logout": true
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="alert {alert-class}">
|
||||
<strong>{title}</strong>
|
||||
<p>{text}</p>
|
||||
<p>
|
||||
<a href="{relative_path}/">[[notifications:back_to_home]]</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
<p>
|
||||
Hello,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To continue with the password reset, please click on the following link:
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
{reset_link}
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
Thanks!<br />
|
||||
<strong>{site_title}</strong>
|
||||
</p>
|
||||
@@ -1,10 +0,0 @@
|
||||
Hello,
|
||||
|
||||
We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.
|
||||
|
||||
To continue with the password reset, please click on the following link:
|
||||
|
||||
{reset_link}
|
||||
|
||||
Thanks!
|
||||
{site_title}
|
||||
@@ -1,20 +0,0 @@
|
||||
<p>
|
||||
Hello {username},
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Thank you for registering with {site_title}!</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To fully activate your account, we need to verify that you own the email address you registered with. Please click on the following link:
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
{confirm_link}
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
Thanks!<br />
|
||||
<strong>{site_title}</strong>
|
||||
</p>
|
||||
@@ -1,11 +0,0 @@
|
||||
Hello {username},
|
||||
|
||||
Thank you for registering with {site_title}!
|
||||
|
||||
To fully activate your account, we need to verify that you own the email address you registered with. Please click on the following link:
|
||||
|
||||
{confirm_link}
|
||||
|
||||
|
||||
Thanks!
|
||||
{site_title}
|
||||
@@ -1,45 +0,0 @@
|
||||
<div class="account-username-box clearfix" data-userslug="{userslug}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="favourites">
|
||||
|
||||
<!-- IF !posts.length -->
|
||||
<div class="alert alert-warning">[[topic:favourites.has_no_favourites]]</div>
|
||||
<!-- ENDIF !posts.length -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 user-favourite-posts" data-nextstart="{nextStart}">
|
||||
<!-- BEGIN posts -->
|
||||
<div class="topic-row panel panel-default clearfix">
|
||||
<div class="panel-body">
|
||||
<a href="../../user/{posts.userslug}">
|
||||
<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}">
|
||||
</a>
|
||||
|
||||
<a href="../../user/{posts.userslug}">
|
||||
<strong><span>{posts.username}</span></strong>
|
||||
</a>
|
||||
<div class="content">{posts.content}</div>
|
||||
|
||||
<div>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
<a href="../../topic/{posts.topic.slug}#{posts.pid}">[[global:posted]]</a>
|
||||
[[global:in]]
|
||||
<a href="../../category/{posts.category.slug}">
|
||||
<i class="fa {posts.category.icon}"></i> {posts.category.name}
|
||||
</a>
|
||||
<span class="timeago" title="{posts.relativeTime}"></span>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
@@ -1,31 +0,0 @@
|
||||
<div class="account-username-box clearfix" data-userslug="{userslug}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="users account">
|
||||
<div>
|
||||
<!-- BEGIN followers -->
|
||||
<div class="users-box">
|
||||
<a href="../../user/{followers.userslug}"><img src="{followers.picture}" class="img-thumbnail"/></a>
|
||||
<br/>
|
||||
<div class="user-info">
|
||||
<a href="../../user/{followers.userslug}">{followers.username}</a>
|
||||
<br/>
|
||||
<div title="reputation" class="reputation">
|
||||
<i class='fa fa-star'></i>
|
||||
<span class='formatted-number'>{followers.reputation}</span>
|
||||
</div>
|
||||
<div title="post count" class="post-count">
|
||||
<i class='fa fa-pencil'></i>
|
||||
<span class='formatted-number'>{followers.postcount}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END followers -->
|
||||
</div>
|
||||
<div id="no-followers-notice" class="alert alert-warning hide">[[user:has_no_follower]]</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
<input type="hidden" template-variable="followersCount" value="{followersCount}" />
|
||||
@@ -1,32 +0,0 @@
|
||||
<div class="account-username-box clearfix" data-userslug="{userslug}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="users account">
|
||||
<div>
|
||||
<!-- BEGIN following -->
|
||||
<div class="users-box">
|
||||
<a href="../../user/{following.userslug}"><img src="{following.picture}" class="img-thumbnail"/></a>
|
||||
<br/>
|
||||
<div class="user-info">
|
||||
<a href="../../user/{following.userslug}">{following.username}</a>
|
||||
<br/>
|
||||
<div title="reputation" class="reputation">
|
||||
<span class='formatted-number'>{following.reputation}</span>
|
||||
<i class='fa fa-star'></i>
|
||||
</div>
|
||||
<div title="post count" class="post-count">
|
||||
<span class='formatted-number'>{following.postcount}</span>
|
||||
<i class='fa fa-pencil'></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- END following -->
|
||||
</div>
|
||||
<div id="no-following-notice" class="alert alert-warning hide">[[user:follows_no_one]]</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" template-variable="yourid" value="{yourid}" />
|
||||
<input type="hidden" template-variable="theirid" value="{theirid}" />
|
||||
<input type="hidden" template-variable="followingCount" value="{followingCount}" />
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
|
||||
</div><!--END container -->
|
||||
|
||||
<div id="upload-picture-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="Upload Picture" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="myModalLabel">[[user:upload_picture]]</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="uploadForm" action="" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="userPhoto">[[user:upload_a_picture]]</label>
|
||||
<input type="file" id="userPhotoInput" name="userPhoto">
|
||||
<p class="help-block">[[user:image_spec]] <span id="file-size-block" class="hide"> ([[user:max]] <span id="upload-file-size"></span> kbs.)</span></p>
|
||||
</div>
|
||||
<input id="imageUploadCsrf" type="hidden" name="_csrf" value="" />
|
||||
<input type="hidden" id="params" name="params">
|
||||
</form>
|
||||
|
||||
<div id="upload-progress-box" class="progress progress-striped">
|
||||
<div id="upload-progress-bar" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0" aria-valuemin="0">
|
||||
<span class="sr-only"> [[footer:success]]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="alert-status" class="alert alert-info hide"></div>
|
||||
<div id="alert-success" class="alert alert-success hide"></div>
|
||||
<div id="alert-error" class="alert alert-danger hide"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
<button id="pictureUploadSubmitBtn" class="btn btn-primary">[[user:upload_picture]]</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<div id="alert_window"></div>
|
||||
|
||||
|
||||
<footer id="footer" class="container footer hide">
|
||||
{footerHTML}
|
||||
<div class="copyright">
|
||||
Copyright © 2014 <a target="_blank" href="https://www.nodebb.com">NodeBB Forums</a> | <a target="_blank" href="//github.com/designcreateplay/NodeBB/graphs/contributors">Contributors</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
require(['forum/footer']);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,218 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{browserTitle}</title>
|
||||
<!-- BEGIN metaTags -->
|
||||
<meta<!-- IF metaTags.name --> name="{metaTags.name}"<!-- ENDIF metaTags.name --><!-- IF metaTags.property --> property="{metaTags.property}"<!-- ENDIF metaTags.property --><!-- IF metaTags.content --> content="{metaTags.content}"<!-- ENDIF metaTags.content --> />
|
||||
<!-- END metaTags -->
|
||||
<link rel="stylesheet" href="{relative_path}/vendor/fontawesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{relative_path}/stylesheet.css?{cache-buster}" />
|
||||
<!-- IF bootswatchCSS --><link href="{bootswatchCSS}" rel="stylesheet" media="screen"><!-- ENDIF bootswatchCSS -->
|
||||
<!-- BEGIN linkTags -->
|
||||
<link<!-- IF linkTags.link --> link="{linkTags.link}"<!-- ENDIF linkTags.link --><!-- IF linkTags.rel --> rel="{linkTags.rel}"<!-- ENDIF linkTags.rel --><!-- IF linkTags.type --> type="{linkTags.type}"<!-- ENDIF linkTags.type --><!-- IF linkTags.href --> href="{linkTags.href}"<!-- ENDIF linkTags.href --> />
|
||||
<!-- END linkTags -->
|
||||
<!-- IF useCustomCSS -->
|
||||
<style type="text/css">{customCSS}</style>
|
||||
<!-- ENDIF useCustomCSS -->
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/es5-shim/2.3.0/es5-shim.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js"></script>
|
||||
<script>__lt_ie_9__ = 1;</script>
|
||||
<![endif]-->
|
||||
|
||||
<script>
|
||||
var RELATIVE_PATH = "{relative_path}";
|
||||
</script>
|
||||
<script src="{relative_path}/socket.io/socket.io.js"></script>
|
||||
<!-- BEGIN clientScripts -->
|
||||
<script src="{relative_path}/{clientScripts.script}?{cache-buster}"></script>
|
||||
<!-- END clientScripts -->
|
||||
<script>
|
||||
require.config({
|
||||
baseUrl: "{relative_path}/src/modules",
|
||||
waitSeconds: 3,
|
||||
urlArgs: "{cache-buster}",
|
||||
paths: {
|
||||
"forum": '../forum'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navbar navbar-inverse navbar-fixed-top header" role="navigation" id="header-menu">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<div>
|
||||
<a href="{relative_path}/">
|
||||
<img class="{brand:logo:display} forum-logo" src="{brand:logo}" />
|
||||
</a>
|
||||
<a href="{relative_path}/">
|
||||
<h1 class="navbar-brand forum-title">{title}</h1>
|
||||
</a>
|
||||
|
||||
<div class="header-topic-title visible-xs">
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar-collapse collapse navbar-ex1-collapse">
|
||||
<ul id="main-nav" class="nav navbar-nav pull-left">
|
||||
<li class="nodebb-loggedin">
|
||||
<a href="{relative_path}/unread"><i id="unread-count" class="fa fa-fw fa-inbox" data-content="0" title="[[global:header.unread]]"></i><span class="visible-xs-inline"> [[global:header.unread]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{relative_path}/recent"><i class="fa fa-fw fa-clock-o" title="[[global:header.recent]]"></i><span class="visible-xs-inline"> [[global:header.recent]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{relative_path}/popular"><i class="fa fa-fw fa-fire" title="[[global:header.popular]]"></i><span class="visible-xs-inline"> [[global:header.popular]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{relative_path}/users"><i class="fa fa-fw fa-users" title="[[global:header.users]]"></i><span class="visible-xs-inline"> [[global:header.users]]</span></a>
|
||||
</li>
|
||||
<!-- IF isAdmin -->
|
||||
<li>
|
||||
<a href="{relative_path}/admin"><i class="fa fa-fw fa-cogs" title="[[global:header.admin]]"></i><span class="visible-xs-inline"> [[global:header.admin]]</span></a>
|
||||
</li>
|
||||
<!-- ENDIF isAdmin -->
|
||||
|
||||
<!-- IF searchEnabled -->
|
||||
<li class="visible-xs">
|
||||
<a id="mobile-search-button" href="{relative_path}/search"><i class="fa fa-search fa-fw" title="[[global:header.search]]"></i> [[global:header.search]]</a>
|
||||
</li>
|
||||
<!-- ENDIF searchEnabled -->
|
||||
|
||||
<!-- BEGIN navigation -->
|
||||
<li class="{navigation.class}">
|
||||
<a href="{relative_path}{navigation.route}" title="{navigation.title}">
|
||||
<!-- IF navigation.iconClass -->
|
||||
<i class="fa fa-fw {navigation.iconClass}"></i>
|
||||
<!-- ENDIF navigation.iconClass -->
|
||||
|
||||
<!-- IF navigation.text -->
|
||||
<span class="{navigation.textClass}">{navigation.text}</span>
|
||||
<!-- ENDIF navigation.text -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- END navigation -->
|
||||
</ul>
|
||||
|
||||
<ul id="logged-in-menu" class="nav navbar-nav navbar-right hide pull-right">
|
||||
<li>
|
||||
<a href="#" id="reconnect" class="hide" title="Connection to {title} has been lost, attempting to reconnect..."><i class="fa fa-check"></i></a>
|
||||
</li>
|
||||
|
||||
<li class="notifications dropdown text-center hidden-xs">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="notif_dropdown"><i class="fa fa-fw fa-bell-o" data-content="0" title="[[global:header.notifications]]"></i></a>
|
||||
<ul id="notif-list" class="dropdown-menu" aria-labelledby="notif_dropdown">
|
||||
<li>
|
||||
<a href="#"><i class="fa fa-refresh fa-spin"></i> [[global:notifications.loading]]</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="visible-xs">
|
||||
<a href="{relative_path}/notifications"><i class="fa fa-exclamation-triangle fa-fw" title="[[notifications:title]]"></i> [[notifications:title]]</a>
|
||||
</li>
|
||||
|
||||
<li class="chats dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="chat_dropdown"><i class="fa fa-comment-o fa-fw" title="[[global:header.chats]]"></i> <span class="visible-xs-inline">[[global:header.chats]]</span></a>
|
||||
<ul id="chat-list" class="dropdown-menu" aria-labelledby="chat_dropdown">
|
||||
<li>
|
||||
<a href="#"><i class="fa fa-refresh fa-spin"></i> [[global:chats.loading]]</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li id="user_label" class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="user_dropdown" title="[[global:header.profile]]">
|
||||
<img src=""/>
|
||||
</a>
|
||||
<ul id="user-control-list" class="dropdown-menu" aria-labelledby="user_dropdown">
|
||||
<li>
|
||||
<a id="user-profile-link" href=""><i class="fa fa-circle status-offline"></i><span>[[global:header.profile]]</span></a>
|
||||
</li>
|
||||
<li id="logout-link">
|
||||
<a href="#">[[global:logout]]</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li>
|
||||
<a href="#" class="user-status" data-status="online"><i class="fa fa-circle status online"></i><span> [[global:online]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="user-status" data-status="away"><i class="fa fa-circle status away"></i><span> [[global:away]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="user-status" data-status="dnd"><i class="fa fa-circle status dnd"></i><span> [[global:dnd]]</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="user-status" data-status="offline"><i class="fa fa-circle status offline"></i><span> [[global:invisible]]</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul id="logged-out-menu" class="nav navbar-nav navbar-right pull-right">
|
||||
<!-- IF allowRegistration -->
|
||||
<li>
|
||||
<a href="{relative_path}/register">
|
||||
<i class="fa fa-pencil visible-xs-inline"></i>
|
||||
<span>[[global:register]]</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- ENDIF allowRegistration -->
|
||||
<li>
|
||||
<a href="{relative_path}/login">
|
||||
<i class="fa fa-sign-in visible-xs-inline"></i>
|
||||
<span>[[global:login]]</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- IF searchEnabled -->
|
||||
<ul id="logged-conditional-menu" class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<form id="search-form" class="navbar-form navbar-right hidden-xs" role="search" method="GET" action="">
|
||||
<div class="hide" id="search-fields">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="[[global:search]]" name="query" value="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default hide">[[global:search]]</button>
|
||||
</div>
|
||||
<button id="search-button" type="button" class="btn btn-link hide"><i class="fa fa-search fa-fw" title="[[global:header.search]]"></i></button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- ENDIF searchEnabled -->
|
||||
|
||||
<ul class="nav navbar-nav navbar-right pagination-block hidden visible-lg visible-md">
|
||||
<li class="active">
|
||||
<a href="#">
|
||||
<i class="fa fa-chevron-up pointer"></i>
|
||||
<span id="pagination"></span>
|
||||
<i class="fa fa-chevron-down pointer"></i>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar"></div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="header-topic-title pull-right hidden-xs">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input id="csrf_token" type="hidden" template-variable="csrf" value="{csrf}" />
|
||||
|
||||
<div class="container" id="content">
|
||||
@@ -1,58 +0,0 @@
|
||||
<div widget-area="motd" class="hidden">
|
||||
<!-- BEGIN widgets -->
|
||||
<div class="motd">
|
||||
{widgets.html}
|
||||
</div>
|
||||
<!-- END widgets -->
|
||||
</div>
|
||||
|
||||
<div class="row home" itemscope itemtype="http://www.schema.org/ItemList">
|
||||
<!-- BEGIN categories -->
|
||||
<div class="{categories.class}">
|
||||
<meta itemprop="name" content="{categories.name}">
|
||||
<h4>
|
||||
<!-- IF !categories.link -->
|
||||
<span class="badge {categories.unread-class}">{categories.topic_count} </span>
|
||||
<!-- ENDIF !categories.link -->
|
||||
|
||||
<!-- IF categories.link -->
|
||||
<a href="{categories.link}" itemprop="url" target="_blank">
|
||||
<!-- ELSE -->
|
||||
<a href="{relative_path}/category/{categories.slug}" itemprop="url">
|
||||
<!-- ENDIF categories.link -->
|
||||
{categories.name}
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
<!-- IF categories.link -->
|
||||
<a href="{categories.link}" itemprop="url" target="_blank">
|
||||
<!-- ELSE -->
|
||||
<a href="{relative_path}/category/{categories.slug}" itemprop="url">
|
||||
<!-- ENDIF categories.link -->
|
||||
<div class="category-header icon category-header-image-{categories.imageClass}" style="background: {categories.background}; color: {categories.color};">
|
||||
<div id="category-{categories.cid}" class="category-slider-{categories.post_count}">
|
||||
<div class="category-box"><i class="fa {categories.icon} fa-4x"></i></div>
|
||||
<div class="category-box" itemprop="description">{categories.description}</div>
|
||||
<!-- BEGIN posts -->
|
||||
<div class="category-box">
|
||||
<div class="post-preview">
|
||||
<img src="{categories.posts.picture}" class="pull-left" />
|
||||
<p class=""><strong>{categories.posts.username}</strong>: {categories.posts.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
<div class="category-box"><i class="fa {categories.icon} fa-4x"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- END categories -->
|
||||
</div>
|
||||
|
||||
<div widget-area="footer" class="hidden">
|
||||
<!-- BEGIN widgets -->
|
||||
<div class="footer">
|
||||
{widgets.html}
|
||||
</div>
|
||||
<!-- END widgets -->
|
||||
</div>
|
||||
@@ -1,68 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">[[login:login]]</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="{login_window:spansize}">
|
||||
<div class="well well-lg">
|
||||
<div class="alert alert-danger" id="login-error-notify" style="display:none">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong>[[login:failed_login_attempt]]</strong> <p></p>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label for="username" class="col-lg-2 control-label">[[login:username]]</label>
|
||||
<div class="col-lg-10">
|
||||
<input class="form-control" type="text" placeholder="[[login:username]]" name="username" id="username" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="col-lg-2 control-label">[[login:password]]</label>
|
||||
<div class="col-lg-10">
|
||||
<input class="form-control" type="password" placeholder="[[login:password]]" name="password" id="password" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-offset-2 col-lg-10">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="remember" id="remember" checked /> [[login:remember_me]]
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-offset-2 col-lg-10">
|
||||
<hr />
|
||||
<button class="btn btn-primary btn-lg btn-block" id="login" type="submit">[[login:login]]</button>
|
||||
<!-- IF showResetLink -->
|
||||
<a id="reset-link" href="{relative_path}/reset">[[login:forgot_password]]</a>
|
||||
<!-- ENDIF showResetLink -->
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_csrf" value="{token}" id="csrf-token" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IF alternate_logins -->
|
||||
<div class="col-md-6">
|
||||
<div class="well well-lg">
|
||||
<h4>[[login:alternative_logins]]</h4>
|
||||
<ul class="alt-logins">
|
||||
<!-- BEGIN authentication -->
|
||||
<li class="{authentication.name}"><a rel="nofollow" href="{authentication.url}"><i class="fa fa-{authentication.icon}-square fa-3x"></i></a></li>
|
||||
<!-- END authentication -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ENDIF alternate_logins -->
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">[[global:logout]]</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-success" id="message">
|
||||
<h4>[[global:logout.title]]</h4>
|
||||
<p>[[global:logout.message]]</p>
|
||||
</div>
|
||||
@@ -1,28 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">{name} <a target="_blank" href="../{cid}.rss"><i class="fa fa-rss-square"></i></a></span>
|
||||
</li>
|
||||
</ol>
|
||||
<ul class="topics" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}">
|
||||
<!-- BEGIN topics -->
|
||||
<li itemprop="itemListElement">
|
||||
<meta itemprop="name" content="{topics.title}">
|
||||
<span class="timestamp">{topics.teaser.timestamp}</span>
|
||||
<a href="../../topic/{topics.slug}" itemprop="url">{topics.title} ({topics.postcount})</a>
|
||||
<div class="teaser">
|
||||
<img class="img-thumbnail" src="{topics.teaser.picture}" />
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
<div class="text-center">
|
||||
<ul class="pagination">
|
||||
<!-- BEGIN pages -->
|
||||
<li <!-- IF pages.active -->class="active"<!-- ENDIF pages.active -->><a href="?page={pages.page}">{pages.page}</a></li>
|
||||
<!-- END pages -->
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class="alert alert-danger">
|
||||
<p>
|
||||
Your browser does not seem to support javascript. As a result, your viewing experience will be diminished, and you have been placed in <strong>read-only mode</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Please download a browser that supports javascript, or enable it, if it disabled (i.e. NoScript).
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<ul class="categories">
|
||||
<!-- BEGIN categories -->
|
||||
<li>
|
||||
<meta itemprop="name" content="{categories.name}">
|
||||
<div class="icon"><i class="fa {categories.icon} fa-4x"></i></div>
|
||||
<a href="category/{categories.slug}" itemprop="name">{categories.name}</a>
|
||||
<p itemprop="description">{categories.description}</p>
|
||||
<div class="clear"></div>
|
||||
</li>
|
||||
<!-- END categories -->
|
||||
</ul>
|
||||
@@ -1,37 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/category/{category.slug}" itemprop="url"><span itemprop="title">{category.name}</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">{title} <a target="_blank" href="../{tid}.rss"><i class="fa fa-rss-square"></i></a></span>
|
||||
</li>
|
||||
</ol>
|
||||
<ul class="posts">
|
||||
<!-- BEGIN posts -->
|
||||
<li itemscope itemtype="http://schema.org/Comment">
|
||||
<meta itemprop="datePublished" content="{posts.relativeTime}">
|
||||
<meta itemprop="dateModified" content="{posts.relativeEditTime}">
|
||||
<a name="{posts.pid}"></a>
|
||||
<div class="row">
|
||||
<div class="col-lg-2 profile">
|
||||
<img class="img-thumbnail" src="{posts.picture}" itemprop="image" /><br />
|
||||
<span class="username" itemprop="author">{posts.username}</span>
|
||||
</div>
|
||||
<div class="col-lg-10" itemprop="text">
|
||||
{posts.content}
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END posts -->
|
||||
</ul>
|
||||
<div class="text-center">
|
||||
<ul class="pagination">
|
||||
<!-- BEGIN pages -->
|
||||
<li <!-- IF pages.active -->class="active"<!-- ENDIF pages.active -->><a href="?page={pages.page}">{pages.page}</a></li>
|
||||
<!-- END pages -->
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
<h2>[[notifications:title]]</h2>
|
||||
|
||||
<ul class="notifications-list">
|
||||
<!-- BEGIN notifications -->
|
||||
<li data-nid="{notifications.nid}" class="{notifications.readClass}">
|
||||
<a href="{notifications.path}">{notifications.text}</a>
|
||||
<p class="timestamp">
|
||||
<span class="timeago" title="{notifications.datetimeISO}"></span>
|
||||
</p>
|
||||
</li>
|
||||
<!-- END notifications -->
|
||||
</ul>
|
||||
@@ -1,27 +0,0 @@
|
||||
<div class="outgoing">
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">[[notifications:outgoing_link]]</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="well">
|
||||
<h3>
|
||||
[[notifications:outgoing_link_message]] {title}.
|
||||
</h3>
|
||||
<p>
|
||||
<a href="{url}" rel="nofollow" class="btn btn-primary btn-lg">[[notifications:continue_to]] {url}</a>
|
||||
<a id="return-btn" href="#" class="btn btn-lg btn-warning">[[notifications:return_to]] {title}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#return-btn').on('click', function() {
|
||||
history.back();
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
@@ -1,79 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{relative_path}/">[[global:home]]</a></li>
|
||||
<li class="active">[[global:header.popular]] <a href="{relative_path}/popular.rss"><i class="fa fa-rss-square"></i></a></li>
|
||||
</ol>
|
||||
|
||||
<ul class="nav nav-pills">
|
||||
<li class=''><a href='{relative_path}/popular/posts'>[[global:posts]]</a></li>
|
||||
<li class=''><a href='{relative_path}/popular/views'>[[global:views]]</a></li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
|
||||
<a href="{relative_path}/popular">
|
||||
<div class="alert alert-warning hide" id="new-topics-alert"></div>
|
||||
</a>
|
||||
|
||||
<!-- IF !topics.length -->
|
||||
<div class="alert alert-warning" id="category-no-topics">
|
||||
<strong>There are no popular topics.</strong>
|
||||
</div>
|
||||
<!-- ENDIF !topics.length -->
|
||||
|
||||
<div class="category row">
|
||||
<div class="col-md-12">
|
||||
<ul id="topics-container" data-nextstart="{nextStart}">
|
||||
<!-- BEGIN topics -->
|
||||
<li class="category-item <!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread --> unread<!-- ENDIF topics.unread -->">
|
||||
<div class="col-md-12 col-xs-12 panel panel-default topic-row">
|
||||
<a href="{relative_path}/user/{topics.user.userslug}" class="pull-left">
|
||||
<img class="img-rounded user-img" src="{topics.user.picture}" title="{topics.user.username}" />
|
||||
</a>
|
||||
|
||||
<h3>
|
||||
<a href="{relative_path}/topic/{topics.slug}">
|
||||
<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
|
||||
<span class="topic-title">{topics.title}</span>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<small>
|
||||
<span class="topic-stats">
|
||||
[[category:posts]]
|
||||
<strong class="human-readable-number" title="{topics.postcount}">{topics.postcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span class="topic-stats">
|
||||
[[category:views]]
|
||||
<strong class="human-readable-number" title="{topics.viewcount}">{topics.viewcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span>
|
||||
[[category:posted]] [[global:in]]
|
||||
<a href="{relative_path}/category/{topics.category.slug}">
|
||||
<i class="fa {topics.category.icon}"></i> {topics.category.name}
|
||||
</a>
|
||||
<span class="timeago" title="{topics.relativeTime}"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="pull-right">
|
||||
<!-- IF topics.unreplied -->
|
||||
[[category:no_replies]]
|
||||
<!-- ELSE -->
|
||||
<a href="{relative_path}/user/{topics.teaser.userslug}">
|
||||
<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
|
||||
</a>
|
||||
<a href="{relative_path}/topic/{topics.slug}#{topics.teaser.pid}">
|
||||
[[category:replied]]
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser.timestamp}"></span>
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,78 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{relative_path}/">[[global:home]]</a></li>
|
||||
<li class="active">[[recent:title]] <a href="{relative_path}/recent.rss"><i class="fa fa-rss-square"></i></a></li>
|
||||
</ol>
|
||||
|
||||
<ul class="nav nav-pills">
|
||||
<li class=''><a href='{relative_path}/recent/day'>[[recent:day]]</a></li>
|
||||
<li class=''><a href='{relative_path}/recent/week'>[[recent:week]]</a></li>
|
||||
<li class=''><a href='{relative_path}/recent/month'>[[recent:month]]</a></li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
|
||||
<a href="{relative_path}/recent">
|
||||
<div class="alert alert-warning hide" id="new-topics-alert"></div>
|
||||
</a>
|
||||
|
||||
<!-- IF !topics.length -->
|
||||
<div class="alert alert-warning" id="category-no-topics">
|
||||
<strong>[[recent:no_recent_topics]]</strong>
|
||||
</div>
|
||||
<!-- ENDIF !topics.length -->
|
||||
|
||||
<div class="category row">
|
||||
<div class="col-md-12">
|
||||
<ul id="topics-container" data-nextstart="{nextStart}">
|
||||
<!-- BEGIN topics -->
|
||||
<li class="category-item <!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted --><!-- IF topics.unread --> unread<!-- ENDIF topics.unread -->">
|
||||
<div class="col-md-12 col-xs-12 panel panel-default topic-row">
|
||||
<a href="{relative_path}/user/{topics.user.userslug}" class="pull-left">
|
||||
<img class="img-rounded user-img" src="{topics.user.picture}" title="{topics.user.username}" />
|
||||
</a>
|
||||
|
||||
<h3>
|
||||
<a href="{relative_path}/topic/{topics.slug}">
|
||||
<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
|
||||
<span class="topic-title">{topics.title}</span>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<small>
|
||||
<span class="topic-stats">
|
||||
[[category:posts]]
|
||||
<strong class="human-readable-number" title="{topics.postcount}">{topics.postcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span class="topic-stats">
|
||||
[[category:views]]
|
||||
<strong class="human-readable-number" title="{topics.viewcount}">{topics.viewcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span>
|
||||
[[category:posted]] [[global:in]]
|
||||
<a href="{relative_path}/category/{topics.category.slug}"><i class="fa {topics.category.icon}"></i> {topics.category.name}</a>
|
||||
<span class="timeago" title="{topics.relativeTime}"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="pull-right">
|
||||
<!-- IF topics.unreplied -->
|
||||
[[category:no_replies]]
|
||||
<!-- ELSE -->
|
||||
<a href="{relative_path}/user/{topics.teaser.userslug}">
|
||||
<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
|
||||
</a>
|
||||
<a href="{relative_path}/topic/{topics.slug}#{topics.teaser.pid}">
|
||||
[[category:replied]]
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser.timestamp}"></span>
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,100 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">[[register:register]]</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="row">
|
||||
<div class="{register_window:spansize}">
|
||||
<div class="well well-lg">
|
||||
<form class="form-horizontal" role="form" action="{relative_path}/register" method="post">
|
||||
<div class="form-group">
|
||||
<label for="email" class="col-lg-4 control-label">[[register:email_address]]</label>
|
||||
<div class="col-lg-8">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" placeholder="[[register:email_address_placeholder]]" name="email" id="email" />
|
||||
<span class="input-group-addon">
|
||||
<span id="email-notify"><i class="fa fa-circle-o"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="help-block">[[register:help.email]]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username" class="col-lg-4 control-label">[[register:username]]</label>
|
||||
<div class="col-lg-8">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" placeholder="[[register:username_placeholder]]" name="username" id="username" />
|
||||
<span class="input-group-addon">
|
||||
<span id="username-notify"><i class="fa fa-circle-o"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="help-block">[[register:help.username_restrictions, {minimumUsernameLength}, {maximumUsernameLength}]]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="col-lg-4 control-label">[[register:password]]</label>
|
||||
<div class="col-lg-8">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="password" placeholder="[[register:password_placeholder]]" name="password" id="password" />
|
||||
<span class="input-group-addon">
|
||||
<span id="password-notify"><i class="fa fa-circle-o"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="help-block">[[register:help.minimum_password_length, {minimumPasswordLength}]]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password-confirm" class="col-lg-4 control-label">[[register:confirm_password]]</label>
|
||||
<div class="col-lg-8">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="password" placeholder="[[register:confirm_password_placeholder]]" name="password-confirm" id="password-confirm" />
|
||||
<span class="input-group-addon">
|
||||
<span id="password-confirm-notify"><i class="fa fa-circle-o"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- IF termsOfUse -->
|
||||
<div class="form-group">
|
||||
<label class="col-lg-4 control-label"> </label>
|
||||
<div class="col-lg-8">
|
||||
<hr />
|
||||
<strong>[[register:terms_of_use]]</strong>
|
||||
<textarea readonly class="form-control" rows=6>{termsOfUse}</textarea>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="agree-terms" id="agree-terms"> [[register:agree_to_terms_of_use]]
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ENDIF termsOfUse -->
|
||||
<div class="form-group">
|
||||
<div class="col-lg-offset-4 col-lg-8">
|
||||
<hr />
|
||||
<button class="btn btn-primary btn-lg btn-block" id="register" type="submit">[[register:register_now_button]]</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_csrf" value="{token}" />
|
||||
<input id="referrer" type="hidden" name="referrer" value="" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IF alternate_logins -->
|
||||
<div class="col-md-6">
|
||||
<div class="well well-lg">
|
||||
<h4>[[register:alternative_registration]]</h4>
|
||||
<ul class="alt-logins">
|
||||
<!-- BEGIN authentication -->
|
||||
<li class="{authentication.name}"><a rel="nofollow" href="{authentication.url}"><i class="fa fa-{authentication.icon}-square fa-3x"></i></a></li>
|
||||
<!-- END authentication -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ENDIF alternate_logins -->
|
||||
</div>
|
||||
@@ -1,29 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">[[reset_password:reset_password]]</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-info">
|
||||
[[reset_password:enter_email]]
|
||||
</div>
|
||||
|
||||
<div class="well">
|
||||
<div class="alert alert-success" id="success" style="display:none">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong>[[reset_password:password_reset_sent]]</strong>
|
||||
</div>
|
||||
<div class="alert alert-danger" id="error" style="display:none">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong>[[reset_password:invalid_email]]</strong>
|
||||
</div>
|
||||
<form onsubmit="return false;">
|
||||
<input type="text" class="form-control input-block input-lg" placeholder="[[reset_password:enter_email_address]]" id="email" />
|
||||
|
||||
<br />
|
||||
<button class="btn btn-primary btn-block btn-lg" id="reset" type="submit">[[reset_password:reset_password]]</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,36 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/reset" itemprop="url"><span itemprop="title">[[reset_password:reset_password]]</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">[[reset_password:update_password]]</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<div class="well">
|
||||
<div class="alert alert-success" id="success" style="display:none">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong>[[reset_password:password_changed.title]]</strong>
|
||||
<p>[[reset_password:password_changed.message]]</p>
|
||||
</div>
|
||||
<div class="alert alert-warning" id="notice" style="display:none">
|
||||
<strong></strong>
|
||||
<p></p>
|
||||
</div>
|
||||
<div class="alert alert-danger" id="error" style="display:none">
|
||||
<strong>[[reset_password:wrong_reset_code.title]]</strong>
|
||||
<p>[[reset_password:wrong_reset_code.message]]</p>
|
||||
</div>
|
||||
<form onsubmit="return false;" id="reset-form">
|
||||
<label for="password">[[reset_password:new_password]]</label>
|
||||
<input class="form-control input-lg" type="password" placeholder="[[reset_password:new_password]]" id="password" /><br />
|
||||
<label for="repeat">[[reset_password:repeat_password]]</label>
|
||||
<input class="form-control input-lg" type="password" placeholder="[[reset_password:repeat_password]]" id="repeat" /><br />
|
||||
<button class="btn btn-primary btn-lg btn-block" id="reset" type="submit">[[reset_password:reset_password]]</button>
|
||||
</form>
|
||||
</div>
|
||||
<input type="hidden" template-variable="reset_code" value="{reset_code}" />
|
||||
@@ -1,91 +0,0 @@
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{relative_path}/">[[global:home]]</a></li>
|
||||
<li class="active">[[global:search]]</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<form id="mobile-search-form" class="navbar-form navbar-right visible-xs" role="search" method="GET" action="">
|
||||
<div class="" id="search-fields">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="[[global:search]]" name="query" value="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default hide">[[global:search]]</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="search">
|
||||
<div class="{show_results} row">
|
||||
|
||||
<div id="topic-results" class="col-md-12" data-search-query="{search_query}">
|
||||
|
||||
<h3>[[topic:topics]]</h3>
|
||||
|
||||
<!-- IF topic_matches -->
|
||||
<small>{topic_matches} result(s) matching "{search_query}"</small>
|
||||
<!-- ENDIF topic_matches -->
|
||||
<div class="alert alert-info {show_no_topics}">[[topic:no_topics_found]]</div>
|
||||
|
||||
<!-- BEGIN topics -->
|
||||
<div class="topic-row panel panel-default clearfix">
|
||||
<div class="panel-body">
|
||||
|
||||
|
||||
<a href="../../topic/{topics.slug}" class="search-result-text">
|
||||
<h4>{topics.title}</h4>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
<a href="../../user/{topics.user.userslug}"><img title="{topics.user.username}" class="img-rounded user-img" src="{topics.user.picture}"></a>
|
||||
<a href="../../topic/{topics.slug}"> [[global:posted]]</a>
|
||||
[[global:in]]
|
||||
<a href="../../category/{topics.category.slug}"><i class="fa {topics.category.icon}"></i> {topics.category.name}</a>
|
||||
<span class="timeago" title="{topics.relativeTime}"></span>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END topics -->
|
||||
</div>
|
||||
|
||||
<div id="post-results" class="col-md-12" data-search-query="{search_query}">
|
||||
<h3>[[topic:posts]]</h3>
|
||||
|
||||
<!-- IF post_matches -->
|
||||
<small>{post_matches} result(s) matching "{search_query}"</small>
|
||||
<!-- ENDIF post_matches -->
|
||||
<div class="alert alert-info {show_no_posts}">[[tropic:no_posts_found]]</div>
|
||||
|
||||
<!-- BEGIN posts -->
|
||||
<div class="topic-row panel panel-default clearfix">
|
||||
<div class="panel-body">
|
||||
|
||||
<a href="../../topic/{posts.topic.slug}#{posts.pid}" class="search-result-text">
|
||||
<h4>{posts.topic.title}</h4>
|
||||
</a>
|
||||
<div class="search-result-text">
|
||||
{posts.content}
|
||||
</div>
|
||||
<div>
|
||||
<small>
|
||||
<span class="pull-right">
|
||||
<a href="../../user/{posts.userslug}">
|
||||
<img title="{posts.username}" class="img-rounded user-img" src="{posts.picture}">
|
||||
</a>
|
||||
<a href="../../topic/{posts.topic.slug}#{posts.pid}"> [[global:posted]]</a>
|
||||
[[global:in]]
|
||||
<a href="../../category/{posts.category.slug}">
|
||||
<i class="fa {posts.category.icon}"></i> {posts.category.name}
|
||||
</a>
|
||||
<span class="timeago" title="{posts.relativeTime}"></span>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,316 +0,0 @@
|
||||
<input type="hidden" template-variable="expose_tools" value="{expose_tools}" />
|
||||
<input type="hidden" template-variable="topic_id" value="{tid}" />
|
||||
<input type="hidden" template-variable="currentPage" value="{currentPage}" />
|
||||
<input type="hidden" template-variable="pageCount" value="{pageCount}" />
|
||||
<input type="hidden" template-variable="locked" value="{locked}" />
|
||||
<input type="hidden" template-variable="deleted" value="{deleted}" />
|
||||
<input type="hidden" template-variable="pinned" value="{pinned}" />
|
||||
<input type="hidden" template-variable="topic_name" value="{title}" />
|
||||
<input type="hidden" template-variable="postcount" value="{postcount}" />
|
||||
|
||||
<div class="topic">
|
||||
<ol class="breadcrumb">
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/" itemprop="url"><span itemprop="title">[[global:home]]</span></a>
|
||||
</li>
|
||||
<li itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<a href="{relative_path}/category/{category.slug}" itemprop="url"><span itemprop="title">{category.name}</span></a>
|
||||
</li>
|
||||
<li class="active" itemscope="itemscope" itemtype="http://data-vocabulary.org/Breadcrumb">
|
||||
<span itemprop="title">{title} <a target="_blank" href="../{tid}.rss"><i class="fa fa-rss-square"></i></a></span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<ul id="post-container" class="posts" data-tid="{tid}">
|
||||
<!-- BEGIN posts -->
|
||||
<li class="post-row infiniteloaded" data-pid="{posts.pid}" data-uid="{posts.uid}" data-username="{posts.username}" data-userslug="{posts.userslug}" data-index="{posts.index}" data-deleted="{posts.deleted}" itemscope itemtype="http://schema.org/Comment">
|
||||
<a id="post_anchor_{posts.pid}" name="{posts.pid}"></a>
|
||||
|
||||
<meta itemprop="datePublished" content="{posts.relativeTime}">
|
||||
<meta itemprop="dateModified" content="{posts.relativeEditTime}">
|
||||
|
||||
<div class="col-md-1 profile-image-block hidden-xs hidden-sm sub-post">
|
||||
<a href="{relative_path}/user/{posts.userslug}">
|
||||
<img src="{posts.picture}" align="left" class="img-thumbnail" itemprop="image" />
|
||||
<!-- IF posts.user_banned -->
|
||||
<span class="label label-danger">[[topic:banned]]</span>
|
||||
<!-- ENDIF posts.user_banned -->
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-11 panel panel-default post-block topic-item">
|
||||
|
||||
<a class="main-post avatar" href="{relative_path}/user/{posts.userslug}">
|
||||
<img itemprop="image" src="{posts.picture}" align="left" class="img-thumbnail" width=150 height=150 />
|
||||
</a>
|
||||
<h3 class="main-post">
|
||||
<p id="topic_title_{posts.pid}" class="topic-title" itemprop="name">{title}</p>
|
||||
</h3>
|
||||
|
||||
<div class="topic-buttons">
|
||||
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" type="button" title="[[topic:posted_by]] {posts.username}">
|
||||
<i class="fa fa-circle status offline"></i>
|
||||
<span class="visible-xs-inline visible-md-inline"><img class="" src="{posts.picture}" width=18 height=18 /> </span>
|
||||
<span class="username-field" href="{relative_path}/user/{posts.userslug}" itemprop="author" data-username="{posts.username}">{posts.username} </span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{relative_path}/user/{posts.userslug}"><i class="fa fa-user"></i> [[topic:profile]]</a></li>
|
||||
<li><a href="#" class="chat"><i class="fa fa-comment"></i> [[topic:chat]]</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-default follow hide" type="button" title="[[topic:notify_me]]"><i class="fa fa-eye"></i></button>
|
||||
<button class="btn btn-sm btn-default flag" type="button" title="[[topic:flag_title]]"><i class="fa fa-flag-o"></i></button>
|
||||
<button data-favourited="{posts.favourited}" class="favourite favourite-tooltip btn btn-sm btn-default <!-- IF posts.favourited --> btn-warning <!-- ENDIF posts.favourited -->" type="button">
|
||||
<span class="favourite-text">[[topic:favourite]]</span>
|
||||
<span class="favouriteCount" data-favourites="{posts.reputation}">{posts.reputation}</span>
|
||||
<!-- IF posts.favourited -->
|
||||
<i class="fa fa-star"></i>
|
||||
<!-- ELSE -->
|
||||
<i class="fa fa-star-o"></i>
|
||||
<!-- ENDIF posts.favourited -->
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button class="upvote btn btn-sm btn-default <!-- IF posts.upvoted --> upvoted btn-primary <!-- ENDIF posts.upvoted -->">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</button>
|
||||
<button class="votes btn btn-sm btn-default" data-votes="{posts.votes}" disabled>{posts.votes}</button>
|
||||
<button class="downvote btn btn-sm btn-default <!-- IF posts.downvoted --> downvoted btn-primary <!-- ENDIF posts.downvoted -->">
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- IF privileges.write -->
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-default quote" type="button" title="[[topic:quote]]"><i class="fa fa-quote-left"></i></button>
|
||||
<button class="btn btn-sm btn-primary btn post_reply" type="button">[[topic:reply]] <i class="fa fa-reply"></i></button>
|
||||
</div>
|
||||
<!-- ENDIF privileges.write -->
|
||||
|
||||
<div class="pull-right">
|
||||
<div class="btn-group post-tools">
|
||||
<div class="dropdown share-dropdown">
|
||||
<button title="[[topic:share]]"class="btn btn-sm btn-default share" data-toggle="dropdown" href="#"><i class="fa fa-share-square-o"></i></button>
|
||||
<ul class="dropdown-menu text-center pull-right" role="menu" aria-labelledby="dLabel">
|
||||
<!-- IF !config.disableSocialButtons -->
|
||||
<li class="btn btn-sm btn-default facebook-share" type="button" title=""><i class="fa fa-facebook fa-fw"></i></li>
|
||||
<li class="btn btn-sm btn-default twitter-share" type="button" title=""><i class="fa fa-twitter fa-fw"></i></li>
|
||||
<li class="btn btn-sm btn-default google-share" type="button" title=""><i class="fa fa-google-plus fa-fw"></i></li>
|
||||
<!-- ENDIF !config.disableSocialButtons -->
|
||||
<li>
|
||||
<input type="text" id="post_{posts.pid}_link" value="" class="form-control pull-right post-link" style=""></input>
|
||||
<li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IF posts.display_moderator_tools -->
|
||||
<div class="btn-group post-tools">
|
||||
<div class="dropdown">
|
||||
<button title="[[topic:tools]]" class="btn btn-sm btn-default" data-toggle="dropdown" href="#"><i class="fa fa-gear"></i></button>
|
||||
<ul class="dropdown-menu text-center pull-right" role="menu" aria-labelledby="dLabel">
|
||||
<button class="btn btn-sm btn-default edit" type="button" title="[[topic:edit]]"><i class="fa fa-pencil"></i></button>
|
||||
<button class="btn btn-sm btn-default delete" type="button" title="[[topic:delete]]"><i class="fa fa-trash-o"></i></button>
|
||||
|
||||
<!-- IF posts.display_move_tools -->
|
||||
<button class="btn btn-sm btn-default move" type="button" title="[[topic:move]]"><i class="fa fa-arrows"></i></button>
|
||||
<!-- ENDIF posts.display_move_tools -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ENDIF posts.display_moderator_tools -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content_{posts.pid}" class="post-content" itemprop="text">{posts.content}</div>
|
||||
<!-- IF posts.signature -->
|
||||
<div class="post-signature">{posts.signature}</div>
|
||||
<!-- ENDIF posts.signature -->
|
||||
|
||||
<div class="post-info">
|
||||
<span class="pull-right">
|
||||
[[category:posted]] <span class="relativeTimeAgo timeago" title="{posts.relativeTime}"></span>
|
||||
<!-- IF posts.editor -->
|
||||
<span>| [[category:last_edited_by]] <strong><a href="{relative_path}/user/{posts.editorslug}">{posts.editorname}</a></strong></span>
|
||||
<span class="timeago" title="{posts.relativeEditTime}"></span>
|
||||
<!-- ENDIF posts.editor -->
|
||||
</span>
|
||||
<span class="pull-left">
|
||||
[[topic:reputation]]: <i class='fa fa-star'></i> <span data-reputation="{posts.user_rep}" data-uid="{posts.uid}" class='formatted-number reputation'>{posts.user_rep}</span> | [[topic:posts]]: <i class='fa fa-pencil'></i> <span class='formatted-number user_postcount_{posts.uid}'>{posts.user_postcount}</span>
|
||||
<!-- BEGIN custom_profile_info -->
|
||||
| {posts.custom_profile_info.content}
|
||||
<!-- END custom_profile_info -->
|
||||
</span>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;"></div>
|
||||
</li>
|
||||
|
||||
<!-- IF !posts.index -->
|
||||
<li class="well post-bar" data-index="{posts.index}">
|
||||
<div class="inline-block">
|
||||
<small class="topic-stats">
|
||||
<span>[[category:posts]]</span>
|
||||
<strong><span id="topic-post-count" class="human-readable-number" title="{postcount}">{postcount}</span></strong> |
|
||||
<span>[[category:views]]</span>
|
||||
<strong><span class="human-readable-number" title="{viewcount}">{viewcount}</span></strong> |
|
||||
<span>[[category:browsing]]</span>
|
||||
</small>
|
||||
<div class="thread_active_users active-users inline-block"></div>
|
||||
</div>
|
||||
<div class="topic-main-buttons pull-right inline-block">
|
||||
<!-- IF privileges.write -->
|
||||
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
|
||||
<!-- ENDIF privileges.write -->
|
||||
<div class="btn-group thread-tools hide">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="#" class="markAsUnreadForAll"><i class="fa fa-fw fa-inbox"></i> [[topic:thread_tools.markAsUnreadForAll]]</a></li>
|
||||
<li><a href="#" class="pin_thread"><i class="fa fa-fw fa-thumb-tack"></i> [[topic:thread_tools.pin]]</a></li>
|
||||
<li><a href="#" class="lock_thread"><i class="fa fa-fw fa-lock"></i> [[topic:thread_tools.lock]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="move_thread"><i class="fa fa-fw fa-arrows"></i> [[topic:thread_tools.move]]</a></li>
|
||||
<li><a href="#" class="fork_thread"><i class="fa fa-fw fa-code-fork"></i> [[topic:thread_tools.fork]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="delete_thread"><span class="text-error"><i class="fa fa-fw fa-trash-o"></i> [[topic:thread_tools.delete]]</span></a></li>
|
||||
<!-- BEGIN thread_tools -->
|
||||
<li>
|
||||
<a href="#" class="{thread_tools.class}"><i class="fa fa-fw {thread_tools.icon}"></i> {thread_tools.title}</a>
|
||||
</li>
|
||||
<!-- END thread_tools -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;"></div>
|
||||
</li>
|
||||
<!-- ENDIF !posts.index -->
|
||||
<!-- END posts -->
|
||||
</ul>
|
||||
|
||||
<div class="well col-md-11 col-xs-12 pull-right post-bar bottom-post-bar hide">
|
||||
<div class="topic-main-buttons pull-right inline-block">
|
||||
<div class="loading-indicator" done="0" style="display:none;">
|
||||
<span class="hidden-xs-inline">[[topic:loading_more_posts]]</span> <i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- IF privileges.write -->
|
||||
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
|
||||
<!-- ENDIF privileges.write -->
|
||||
<div class="btn-group thread-tools hide dropup">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="#" class="markAsUnreadForAll"><i class="fa fa-fw fa-inbox"></i> [[topic:thread_tools.markAsUnreadForAll]]</a></li>
|
||||
<li><a href="#" class="pin_thread"><i class="fa fa-fw fa-thumb-tack"></i> [[topic:thread_tools.pin]]</a></li>
|
||||
<li><a href="#" class="lock_thread"><i class="fa fa-fw fa-lock"></i> [[topic:thread_tools.lock]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="move_thread"><i class="fa fa-fw fa-arrows"></i> [[topic:thread_tools.move]]</a></li>
|
||||
<li><a href="#" class="fork_thread"><i class="fa fa-fw fa-code-fork"></i> [[topic:thread_tools.fork]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="delete_thread"><span class="text-error"><i class="fa fa-fw fa-trash-o"></i> [[topic:thread_tools.delete]]</span></a></li>
|
||||
<!-- BEGIN thread_tools -->
|
||||
<li>
|
||||
<a href="#" class="{thread_tools.class}"><i class="fa fa-fw {thread_tools.icon}"></i> {thread_tools.title}</a>
|
||||
</li>
|
||||
<!-- END thread_tools -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
|
||||
<!-- IF config.usePagination -->
|
||||
<div class="text-center">
|
||||
<ul class="pagination">
|
||||
<li class="previous pull-left"><a href="#"><i class="fa fa-chevron-left"></i> [[global:previouspage]]</a></li>
|
||||
<li class="next pull-right"><a href="#">[[global:nextpage]] <i class="fa fa-chevron-right"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- ENDIF config.usePagination -->
|
||||
|
||||
<div id="move_thread_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Move Topic" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>[[topic:move_topic]]</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="categories-loading"><i class="fa fa-spin fa-refresh"></i> [[topic:load_categories]]</p>
|
||||
<ul class="category-list"></ul>
|
||||
<p>
|
||||
[[topic:disabled_categories_note]]
|
||||
</p>
|
||||
<div id="move-confirm" style="display: none;">
|
||||
<hr />
|
||||
<div class="alert alert-info">[[topic:topic_will_be_moved_to]] <strong><span id="confirm-category-name"></span></strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="move_thread_cancel">[[global:buttons.close]]</button>
|
||||
<button type="button" class="btn btn-primary" id="move_thread_commit" disabled>[[topic:confirm_move]]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="fork-thread-modal" class="hide" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true" data-backdrop="none">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4>[[topic:fork_topic]]</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input id="fork-title" type="text" class="form-control" placeholder="Enter new thread title"><br/>
|
||||
<label>[[topic:fork_topic_instruction]]</label> <br/>
|
||||
<span id="fork-pids"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="fork_thread_cancel">[[global:buttons.close]]</button>
|
||||
<button type="button" class="btn btn-primary" id="fork_thread_commit" disabled>[[topic:confirm_fork]]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="move-post-modal" class="hide" tabindex="-1" role="dialog" aria-labelledby="" aria-hidden="true" data-backdrop="none">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4>[[topic:move_post]]</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="topicId">Topic ID</label>
|
||||
<input id="topicId" type="text" class="form-control" placeholder="Enter topic ID"><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="move_post_cancel">[[global:buttons.close]]</button>
|
||||
<button type="button" class="btn btn-primary" id="move_post_commit" disabled>[[topic:confirm_move]]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div widget-area="footer" class="col-xs-12">
|
||||
<!-- BEGIN widgets -->
|
||||
{widgets.html}
|
||||
<!-- END widgets -->
|
||||
</div>
|
||||
@@ -1,73 +0,0 @@
|
||||
<div class="unread">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{relative_path}/">[[global:home]]</a></li>
|
||||
<li class="active">[[unread:title]]</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<div class="alert alert-warning {no_topics_message}" id="category-no-topics">
|
||||
<strong>[[unread:no_unread_topics]]</strong>
|
||||
</div>
|
||||
|
||||
<button id="mark-allread-btn" class="btn btn-primary {show_markallread_button}">[[unread:mark_all_read]]</button>
|
||||
|
||||
<a href="{relative_path}/unread">
|
||||
<div class="alert alert-warning hide" id="new-topics-alert"></div>
|
||||
</a>
|
||||
|
||||
<div class="category row">
|
||||
<div class="col-md-12">
|
||||
<ul id="topics-container" data-nextstart="{nextStart}">
|
||||
<!-- BEGIN topics -->
|
||||
<li class="category-item<!-- IF topics.deleted --> deleted<!-- ENDIF topics.deleted -->" data-tid="{topics.tid}">
|
||||
<div class="col-md-12 col-xs-12 panel panel-default topic-row">
|
||||
<a href="{relative_path}/user/{topics.user.userslug}" class="pull-left">
|
||||
<img class="img-rounded user-img" src="{topics.user.picture}" title="{topics.user.username}" />
|
||||
</a>
|
||||
<h3>
|
||||
<a href="{relative_path}/topic/{topics.slug}">
|
||||
<strong><!-- IF topics.pinned --><i class="fa fa-thumb-tack"></i><!-- ENDIF topics.pinned --> <!-- IF topics.locked --><i class="fa fa-lock"></i><!-- ENDIF topics.locked --></strong>
|
||||
<span class="topic-title">{topics.title}</span>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<small>
|
||||
<span class="topic-stats">
|
||||
[[category:posts]]
|
||||
<strong class="human-readable-number" title="{topics.postcount}">{topics.postcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span class="topic-stats">
|
||||
[[category:views]]
|
||||
<strong class="human-readable-number" title="{topics.viewcount}">{topics.viewcount}</strong>
|
||||
</span>
|
||||
|
|
||||
<span>
|
||||
[[category:posted]] [[global:in]]
|
||||
<a href="{relative_path}/category/{topics.category.slug}"><i class="fa {topics.category.icon}"></i> {topics.category.name}</a>
|
||||
<span class="timeago" title="{topics.relativeTime}"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="pull-right">
|
||||
<!-- IF topics.unreplied -->
|
||||
[[category:no_replies]]
|
||||
<!-- ELSE -->
|
||||
<a href="{relative_path}/user/{topics.teaser.userslug}">
|
||||
<img class="teaser-pic" src="{topics.teaser.picture}" title="{topics.teaser.username}"/>
|
||||
</a>
|
||||
<a href="{relative_path}/topic/{topics.slug}#{topics.teaser.pid}">
|
||||
[[category:replied]]
|
||||
</a>
|
||||
<span class="timeago" title="{topics.teaser.timestamp}"></span>
|
||||
<!-- ENDIF topics.unreplied -->
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END topics -->
|
||||
</ul>
|
||||
<button id="load-more-btn" class="btn btn-primary hide">[[unread:load_more]]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,57 +0,0 @@
|
||||
<div class="users">
|
||||
<ul class="nav nav-pills">
|
||||
<li class=''><a href='{relative_path}/users/online'>[[users:online]]</a></li>
|
||||
<li class=''><a href='{relative_path}/users/latest'>[[users:latest_users]]</a></li>
|
||||
<li class=''><a href='{relative_path}/users/sort-posts'>[[users:top_posters]]</a></li>
|
||||
<li class=''><a href='{relative_path}/users/sort-reputation'>[[users:most_reputation]]</a></li>
|
||||
<li class=''><a href='{relative_path}/users/search'>[[users:search]]</a></li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="search {search_display} well">
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="search-user" type="text" placeholder="[[users:enter_username]]"/>
|
||||
<span class="input-group-addon">
|
||||
<span id="user-notfound-notify"><i class="fa fa-search"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="users-container" class="users-container">
|
||||
<!-- BEGIN users -->
|
||||
<li class="users-box registered-user">
|
||||
<a href="{relative_path}/user/{users.userslug}"><img src="{users.picture}" class="img-thumbnail"/></a>
|
||||
<br/>
|
||||
<div class="user-info">
|
||||
<span>
|
||||
<i class="fa fa-circle status {users.status}" title="[[global:{users.status}]]"></i>
|
||||
<a href="{relative_path}/user/{users.userslug}">{users.username}</a>
|
||||
</span>
|
||||
<br/>
|
||||
<div title="reputation" class="reputation">
|
||||
<i class='fa fa-star'></i>
|
||||
<span class='formatted-number'>{users.reputation}</span>
|
||||
</div>
|
||||
<div title="post count" class="post-count">
|
||||
<i class='fa fa-pencil'></i>
|
||||
<span class='formatted-number'>{users.postcount}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END users -->
|
||||
<li class="users-box {show_anon} anon-user">
|
||||
<img src="https://secure.gravatar.com/avatar/" class="img-thumbnail"/>
|
||||
<br/>
|
||||
<div class="user-info">
|
||||
<span id="online_anon_count">{anonymousUserCount}</span>
|
||||
<span>Anonymous</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="text-center {loadmore_display}">
|
||||
<button id="load-more-users-btn" class="btn btn-primary">[[users:load_more]]</button>
|
||||
</div>
|
||||
</div>
|
||||
444
src/controllers/accounts.js
Normal file
444
src/controllers/accounts.js
Normal file
@@ -0,0 +1,444 @@
|
||||
"use strict";
|
||||
|
||||
var accountsController = {};
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
winston = require('winston'),
|
||||
nconf = require('nconf'),
|
||||
async= require('async'),
|
||||
|
||||
user = require('./../user'),
|
||||
posts = require('./../posts'),
|
||||
postTools = require('../postTools'),
|
||||
utils = require('./../../public/src/utils'),
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins'),
|
||||
image = require('./../image'),
|
||||
file = require('./../file');
|
||||
|
||||
function userNotFound(res) {
|
||||
return res.render('404', {
|
||||
error: 'User not found!'
|
||||
});
|
||||
}
|
||||
|
||||
function userNotAllowed(res) {
|
||||
return res.render('403', {
|
||||
error: 'Not allowed.'
|
||||
});
|
||||
}
|
||||
|
||||
function getUserDataByUserSlug(userslug, callerUID, callback) {
|
||||
user.getUidByUserslug(userslug, function(err, uid) {
|
||||
if(err || !uid) {
|
||||
return callback(err || new Error('invalid-user'));
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
userData : function(next) {
|
||||
user.getUserData(uid, next);
|
||||
},
|
||||
userSettings : function(next) {
|
||||
user.getSettings(uid, next);
|
||||
},
|
||||
isAdmin : function(next) {
|
||||
user.isAdministrator(callerUID, next);
|
||||
},
|
||||
followStats: function(next) {
|
||||
user.getFollowStats(uid, next);
|
||||
},
|
||||
ips: function(next) {
|
||||
user.getIPs(uid, 4, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if(err || !results.userData) {
|
||||
return callback(err || new Error('invalid-user'));
|
||||
}
|
||||
|
||||
var userData = results.userData;
|
||||
var userSettings = results.userSettings;
|
||||
var isAdmin = results.isAdmin;
|
||||
var self = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
|
||||
|
||||
userData.joindate = utils.toISOString(userData.joindate);
|
||||
if(userData.lastonline) {
|
||||
userData.lastonline = utils.toISOString(userData.lastonline);
|
||||
} else {
|
||||
userData.lastonline = userData.joindate;
|
||||
}
|
||||
|
||||
if (!userData.birthday) {
|
||||
userData.age = '';
|
||||
} else {
|
||||
userData.age = Math.floor((new Date().getTime() - new Date(userData.birthday).getTime()) / 31536000000);
|
||||
}
|
||||
|
||||
function canSeeEmail() {
|
||||
return ;
|
||||
}
|
||||
|
||||
if (!(isAdmin || self || (userData.email && userSettings.showemail))) {
|
||||
userData.email = "";
|
||||
}
|
||||
|
||||
if (self && !userSettings.showemail) {
|
||||
userData.emailClass = "";
|
||||
} else {
|
||||
userData.emailClass = "hide";
|
||||
}
|
||||
|
||||
if (isAdmin || self) {
|
||||
userData.ips = results.ips;
|
||||
}
|
||||
|
||||
userData.websiteName = userData.website.replace('http://', '').replace('https://', '');
|
||||
userData.banned = parseInt(userData.banned, 10) === 1;
|
||||
userData.uid = userData.uid;
|
||||
userData.yourid = callerUID;
|
||||
userData.theirid = userData.uid;
|
||||
|
||||
userData.disableSignatures = meta.config.disableSignatures !== undefined && parseInt(meta.config.disableSignatures, 10) === 1;
|
||||
|
||||
userData.followingCount = results.followStats.followingCount;
|
||||
userData.followerCount = results.followStats.followerCount;
|
||||
|
||||
callback(null, userData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
accountsController.getUserByUID = function(req, res, next) {
|
||||
var uid = req.params.uid ? req.params.uid : 0;
|
||||
|
||||
user.getUserData(uid, function(err, userData) {
|
||||
res.json(userData);
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.getAccount = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!userData) {
|
||||
return res.json(404, {
|
||||
error: 'User not found!'
|
||||
});
|
||||
}
|
||||
|
||||
user.isFollowing(callerUID, userData.theirid, function (isFollowing) {
|
||||
posts.getPostsByUid(callerUID, userData.theirid, 0, 9, function (err, userPosts) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
userData.posts = userPosts.posts.filter(function (p) {
|
||||
return p && parseInt(p.deleted, 10) !== 1;
|
||||
});
|
||||
|
||||
userData.isFollowing = isFollowing;
|
||||
|
||||
if (!userData.profileviews) {
|
||||
userData.profileviews = 1;
|
||||
}
|
||||
|
||||
if (callerUID !== parseInt(userData.uid, 10) && callerUID) {
|
||||
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
|
||||
}
|
||||
|
||||
postTools.parse(userData.signature, function (err, signature) {
|
||||
userData.signature = signature;
|
||||
|
||||
res.render('account', userData);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.getFollowing = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (userData) {
|
||||
user.getFollowing(userData.uid, function (err, followingData) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
userData.following = followingData;
|
||||
userData.followingCount = followingData.length;
|
||||
|
||||
res.render('following', userData);
|
||||
});
|
||||
|
||||
} else {
|
||||
return userNotFound();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.getFollowers = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (userData) {
|
||||
user.getFollowers(userData.uid, function (err, followersData) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
userData.followers = followersData;
|
||||
userData.followersCount = followersData.length;
|
||||
|
||||
res.render('followers', userData);
|
||||
});
|
||||
} else {
|
||||
return userNotFound();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.getFavourites = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
user.getUidByUserslug(req.params.userslug, function (err, uid) {
|
||||
if (!uid) {
|
||||
return userNotFound();
|
||||
}
|
||||
|
||||
if (parseInt(uid, 10) !== callerUID) {
|
||||
return userNotAllowed();
|
||||
}
|
||||
|
||||
user.getUserFields(uid, ['username', 'userslug'], function (err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
return userNotFound();
|
||||
}
|
||||
|
||||
posts.getFavourites(uid, 0, 9, function (err, favourites) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
userData.theirid = uid;
|
||||
userData.yourid = callerUID;
|
||||
userData.posts = favourites.posts;
|
||||
userData.nextStart = favourites.nextStart;
|
||||
|
||||
res.render('favourites', userData);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.getPosts = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
user.getUidByUserslug(req.params.userslug, function (err, uid) {
|
||||
if (!uid) {
|
||||
return userNotFound();
|
||||
}
|
||||
|
||||
user.getUserFields(uid, ['username', 'userslug'], function (err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
return userNotFound();
|
||||
}
|
||||
|
||||
posts.getPostsByUid(callerUID, uid, 0, 19, function (err, userPosts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
userData.uid = uid;
|
||||
userData.theirid = uid;
|
||||
userData.yourid = callerUID;
|
||||
userData.posts = userPosts.posts;
|
||||
userData.nextStart = userPosts.nextStart;
|
||||
|
||||
res.render('accountposts', userData);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.accountEdit = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
getUserDataByUserSlug(req.params.userslug, callerUID, function (err, userData) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('accountedit', userData);
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.accountSettings = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
user.getUidByUserslug(req.params.userslug, function(err, uid) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!uid) {
|
||||
return userNotFound();
|
||||
}
|
||||
|
||||
if (parseInt(uid, 10) !== callerUID) {
|
||||
return userNotAllowed();
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:user.settings', [], function(err, settings) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
user.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!userData) {
|
||||
return userNotFound();
|
||||
}
|
||||
userData.yourid = req.user.uid;
|
||||
userData.theirid = uid;
|
||||
userData.settings = settings;
|
||||
|
||||
res.render('accountsettings', userData);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.uploadPicture = function (req, res, next) {
|
||||
var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
|
||||
if (req.files.userPhoto.size > uploadSize * 1024) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
return res.json({
|
||||
error: 'Images must be smaller than ' + uploadSize + ' kb!'
|
||||
});
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
return res.json({
|
||||
error: 'Allowed image types are png, jpg and gif!'
|
||||
});
|
||||
}
|
||||
|
||||
var extension = path.extname(req.files.userPhoto.name);
|
||||
if (!extension) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
return res.json({
|
||||
error: 'Error uploading file! Error : Invalid extension!'
|
||||
});
|
||||
}
|
||||
|
||||
var updateUid = req.user.uid;
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
image.resizeImage(req.files.userPhoto.path, extension, 128, 128, next);
|
||||
},
|
||||
function(next) {
|
||||
image.convertImageToPng(req.files.userPhoto.path, extension, next);
|
||||
},
|
||||
function(next) {
|
||||
try {
|
||||
var params = JSON.parse(req.body.params);
|
||||
if(parseInt(updateUid, 10) === parseInt(params.uid, 10)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
user.isAdministrator(req.user.uid, function(err, isAdmin) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!isAdmin) {
|
||||
return userNotAllowed();
|
||||
}
|
||||
updateUid = params.uid;
|
||||
next();
|
||||
});
|
||||
} catch(err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
], function(err, result) {
|
||||
|
||||
function done(err, image) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
if(err) {
|
||||
return res.json({error: err.message});
|
||||
}
|
||||
|
||||
user.setUserField(updateUid, 'uploadedpicture', image.url);
|
||||
user.setUserField(updateUid, 'picture', image.url);
|
||||
res.json({
|
||||
path: image.url
|
||||
});
|
||||
}
|
||||
|
||||
if(err) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
return res.json({error:err.message});
|
||||
}
|
||||
|
||||
if(plugins.hasListeners('filter:uploadImage')) {
|
||||
return plugins.fireHook('filter:uploadImage', req.files.userPhoto, done);
|
||||
}
|
||||
|
||||
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10);
|
||||
var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension);
|
||||
|
||||
user.getUserField(updateUid, 'uploadedpicture', function (err, oldpicture) {
|
||||
if (!oldpicture) {
|
||||
file.saveFileToLocal(filename, req.files.userPhoto.path, done);
|
||||
return;
|
||||
}
|
||||
|
||||
var absolutePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), path.basename(oldpicture));
|
||||
|
||||
fs.unlink(absolutePath, function (err) {
|
||||
if (err) {
|
||||
winston.err(err);
|
||||
}
|
||||
|
||||
file.saveFileToLocal(filename, req.files.userPhoto.path, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
accountsController.getNotifications = function(req, res, next) {
|
||||
user.notifications.getAll(req.user.uid, null, null, function(err, notifications) {
|
||||
res.render('notifications', {
|
||||
notifications: notifications
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = accountsController;
|
||||
217
src/controllers/admin.js
Normal file
217
src/controllers/admin.js
Normal file
@@ -0,0 +1,217 @@
|
||||
"use strict";
|
||||
|
||||
var async = require('async'),
|
||||
|
||||
user = require('./../user'),
|
||||
categories = require('./../categories'),
|
||||
topics = require('./../topics'),
|
||||
db = require('./../database'),
|
||||
events = require('./../events'),
|
||||
languages = require('./../languages'),
|
||||
plugins = require('./../plugins'),
|
||||
widgets = require('./../widgets'),
|
||||
groups = require('./../groups'),
|
||||
pkg = require('./../../package.json');
|
||||
|
||||
var adminController = {
|
||||
users: {},
|
||||
categories: {},
|
||||
topics: {},
|
||||
groups: {},
|
||||
themes: {},
|
||||
events: {},
|
||||
database: {},
|
||||
plugins: {},
|
||||
languages: {},
|
||||
settings: {},
|
||||
logger: {}
|
||||
};
|
||||
|
||||
adminController.home = function(req, res, next) {
|
||||
res.render('admin/index', {
|
||||
version: pkg.version,
|
||||
emailerInstalled: plugins.hasListeners('action:email.send'),
|
||||
searchInstalled: plugins.hasListeners('filter:search.query')
|
||||
});
|
||||
};
|
||||
|
||||
adminController.users.search = function(req, res, next) {
|
||||
res.render('admin/users', {
|
||||
search_display: 'block',
|
||||
loadmore_display: 'none',
|
||||
users: []
|
||||
});
|
||||
};
|
||||
|
||||
adminController.users.latest = function(req, res, next) {
|
||||
user.getUsers('users:joindate', 0, 49, function(err, users) {
|
||||
res.render('admin/users', {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: users,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.users.sortByPosts = function(req, res, next) {
|
||||
user.getUsers('users:postcount', 0, 49, function(err, users) {
|
||||
res.render('admin/users', {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: users,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.users.sortByReputation = function(req, res, next) {
|
||||
user.getUsers('users:reputation', 0, 49, function(err, users) {
|
||||
res.render('admin/users', {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: users,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.users.sortByJoinDate = function(req, res, next) {
|
||||
user.getUsers('users:joindate', 0, 49, function(err, users) {
|
||||
res.render('admin/users', {
|
||||
search_display: 'none',
|
||||
users: users,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.categories.active = function(req, res, next) {
|
||||
categories.getAllCategories(0, function (err, data) {
|
||||
data.categories = data.categories.filter(function (category) {
|
||||
return !category.disabled;
|
||||
});
|
||||
|
||||
res.render('admin/categories', data);
|
||||
});
|
||||
};
|
||||
|
||||
adminController.categories.disabled = function(req, res, next) {
|
||||
categories.getAllCategories(0, function (err, data) {
|
||||
data.categories = data.categories.filter(function (category) {
|
||||
return category.disabled;
|
||||
});
|
||||
|
||||
res.render('admin/categories', data);
|
||||
});
|
||||
};
|
||||
|
||||
adminController.database.get = function(req, res, next) {
|
||||
db.info(function (err, data) {
|
||||
res.render('admin/database', data);
|
||||
});
|
||||
};
|
||||
|
||||
// todo: deprecate this seemingly useless view
|
||||
adminController.topics.get = function(req, res, next) {
|
||||
topics.getAllTopics(0, 19, function (err, topics) {
|
||||
res.render('admin/topics', {
|
||||
topics: topics,
|
||||
notopics: topics.length === 0
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.events.get = function(req, res, next) {
|
||||
events.getLog(function(err, data) {
|
||||
if(err || !data) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data = data.toString().split('\n').reverse().join('\n');
|
||||
res.render('admin/events', {
|
||||
eventdata: data
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.plugins.get = function(req, res, next) {
|
||||
plugins.showInstalled(function (err, plugins) {
|
||||
if (err || !Array.isArray(plugins)) {
|
||||
plugins = [];
|
||||
}
|
||||
|
||||
res.render('admin/plugins', {
|
||||
plugins: plugins
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.languages.get = function(req, res, next) {
|
||||
languages.list(function(err, languages) {
|
||||
res.render('admin/languages', {
|
||||
languages: languages
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.settings.get = function(req, res, next) {
|
||||
res.render('admin/settings', {});
|
||||
};
|
||||
|
||||
adminController.logger.get = function(req, res, next) {
|
||||
res.render('admin/logger', {});
|
||||
};
|
||||
|
||||
adminController.themes.get = function(req, res, next) {
|
||||
async.parallel({
|
||||
areas: function(next) {
|
||||
plugins.fireHook('filter:widgets.getAreas', [], next);
|
||||
},
|
||||
widgets: function(next) {
|
||||
plugins.fireHook('filter:widgets.getWidgets', [], next);
|
||||
}
|
||||
}, function(err, widgetData) {
|
||||
async.each(widgetData.areas, function(area, next) {
|
||||
widgets.getArea(area.template, area.location, function(err, areaData) {
|
||||
area.data = areaData;
|
||||
next(err);
|
||||
});
|
||||
}, function(err) {
|
||||
for (var w in widgetData.widgets) {
|
||||
if (widgetData.widgets.hasOwnProperty(w)) {
|
||||
widgetData.widgets[w].content += "<br /><label>Title:</label><input type=\"text\" class=\"form-control\" name=\"title\" placeholder=\"Title (only shown on some containers)\" /><br /><label>Container:</label><textarea rows=\"4\" class=\"form-control container-html\" name=\"container\" placeholder=\"Drag and drop a container or enter HTML here.\"></textarea>";
|
||||
}
|
||||
}
|
||||
|
||||
res.render('admin/themes', {
|
||||
areas: widgetData.areas,
|
||||
widgets: widgetData.widgets
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
adminController.groups.get = function(req, res, next) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
groups.list({
|
||||
expand: true
|
||||
}, next);
|
||||
},
|
||||
function(next) {
|
||||
groups.listSystemGroups({
|
||||
expand: true
|
||||
}, next);
|
||||
}
|
||||
], function(err, groupData) {
|
||||
var groups = groupData[0].concat(groupData[1]);
|
||||
|
||||
res.render('admin/groups', {
|
||||
groups: groups,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = adminController;
|
||||
61
src/controllers/api.js
Normal file
61
src/controllers/api.js
Normal file
@@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
|
||||
var pkg = require('./../../package.json'),
|
||||
meta = require('./../meta'),
|
||||
user = require('./../user'),
|
||||
plugins = require('./../plugins');
|
||||
|
||||
var apiController = {};
|
||||
|
||||
apiController.getConfig = function(req, res, next) {
|
||||
var config = require('./../../public/config.json');
|
||||
|
||||
config.version = pkg.version;
|
||||
config.postDelay = meta.config.postDelay;
|
||||
config.minimumTitleLength = meta.config.minimumTitleLength;
|
||||
config.maximumTitleLength = meta.config.maximumTitleLength;
|
||||
config.minimumPostLength = meta.config.minimumPostLength;
|
||||
config.hasImageUploadPlugin = plugins.hasListeners('filter:uploadImage');
|
||||
config.maximumProfileImageSize = meta.config.maximumProfileImageSize;
|
||||
config.minimumUsernameLength = meta.config.minimumUsernameLength;
|
||||
config.maximumUsernameLength = meta.config.maximumUsernameLength;
|
||||
config.minimumPasswordLength = meta.config.minimumPasswordLength;
|
||||
config.maximumSignatureLength = meta.config.maximumSignatureLength;
|
||||
config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1;
|
||||
config.allowGuestPosting = parseInt(meta.config.allowGuestPosting, 10) === 1;
|
||||
config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1;
|
||||
config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1;
|
||||
config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
|
||||
config.disableSocialButtons = parseInt(meta.config.disableSocialButtons, 10) === 1;
|
||||
config.topicsPerPage = meta.config.topicsPerPage || 20;
|
||||
config.postsPerPage = meta.config.postsPerPage || 20;
|
||||
config.maximumFileSize = meta.config.maximumFileSize;
|
||||
config['theme:id'] = meta.config['theme:id'];
|
||||
config.defaultLang = meta.config.defaultLang || 'en_GB';
|
||||
config.environment = process.env.NODE_ENV;
|
||||
|
||||
if (!req.user) {
|
||||
if (res.locals.isAPI) {
|
||||
res.json(200, config);
|
||||
} else {
|
||||
next(null, config);
|
||||
}
|
||||
}
|
||||
|
||||
if(req.user) {
|
||||
user.getSettings(req.user.uid, function(err, settings) {
|
||||
config.usePagination = settings.usePagination;
|
||||
config.topicsPerPage = settings.topicsPerPage;
|
||||
config.postsPerPage = settings.postsPerPage;
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
res.json(200, config);
|
||||
} else {
|
||||
next(err, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = apiController;
|
||||
173
src/controllers/categories.js
Normal file
173
src/controllers/categories.js
Normal file
@@ -0,0 +1,173 @@
|
||||
"use strict";
|
||||
|
||||
var categoriesController = {},
|
||||
async = require('async'),
|
||||
qs = require('querystring'),
|
||||
nconf = require('nconf'),
|
||||
categoryTools = require('./../categoryTools'),
|
||||
user = require('./../user'),
|
||||
categories = require('./../categories'),
|
||||
topics = require('./../topics');
|
||||
|
||||
categoriesController.recent = function(req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('recent', data);
|
||||
});
|
||||
};
|
||||
|
||||
categoriesController.popular = function(req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
var set = 'topics:' + req.params.set;
|
||||
if(!req.params.set) {
|
||||
set = 'topics:posts';
|
||||
}
|
||||
|
||||
topics.getTopicsFromSet(uid, set, 0, 19, function(err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('popular', data);
|
||||
});
|
||||
};
|
||||
|
||||
categoriesController.unread = function(req, res, next) {
|
||||
var uid = req.user.uid;
|
||||
|
||||
topics.getUnreadTopics(uid, 0, 19, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('unread', data);
|
||||
});
|
||||
};
|
||||
|
||||
categoriesController.unreadTotal = function(req, res, next) {
|
||||
var uid = req.user.uid;
|
||||
|
||||
topics.getTotalUnread(uid, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('unread', data);
|
||||
});
|
||||
};
|
||||
|
||||
categoriesController.get = function(req, res, next) {
|
||||
var cid = req.params.category_id,
|
||||
page = req.query.page || 1,
|
||||
uid = req.user ? req.user.uid : 0,
|
||||
privileges = null;
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
categoryTools.privileges(cid, uid, function(err, categoryPrivileges) {
|
||||
if (!err) {
|
||||
if (!categoryPrivileges.read) {
|
||||
next(new Error('not-enough-privileges'));
|
||||
} else {
|
||||
privileges = categoryPrivileges;
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
user.getSettings(uid, function(err, settings) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var start = (page - 1) * settings.topicsPerPage,
|
||||
end = start + settings.topicsPerPage - 1;
|
||||
|
||||
categories.getCategoryById(cid, start, end, 0, function (err, categoryData) {
|
||||
if (categoryData) {
|
||||
if (parseInt(categoryData.disabled, 10) === 1) {
|
||||
return next(new Error('Category disabled'), null);
|
||||
}
|
||||
}
|
||||
|
||||
next(err, categoryData);
|
||||
});
|
||||
});
|
||||
},
|
||||
function (categoryData, next) {
|
||||
res.locals.metaTags = [
|
||||
{
|
||||
name: 'title',
|
||||
content: categoryData.name
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: categoryData.name
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
content: categoryData.description
|
||||
},
|
||||
{
|
||||
property: "og:type",
|
||||
content: 'website'
|
||||
}
|
||||
];
|
||||
|
||||
res.locals.linkTags = [
|
||||
{
|
||||
rel: 'alternate',
|
||||
type: 'application/rss+xml',
|
||||
href: nconf.get('url') + '/category/' + cid + '.rss'
|
||||
},
|
||||
{
|
||||
rel: 'up',
|
||||
href: nconf.get('url')
|
||||
}
|
||||
];
|
||||
|
||||
next(null, categoryData);
|
||||
}
|
||||
], function (err, data) {
|
||||
if (err) {
|
||||
if (err.message === 'not-enough-privileges') {
|
||||
return res.redirect('403');
|
||||
} else {
|
||||
return res.redirect('404');
|
||||
}
|
||||
}
|
||||
|
||||
if(data.link) {
|
||||
return res.redirect(data.link);
|
||||
}
|
||||
|
||||
var category_url = cid + (req.params.slug ? '/' + req.params.slug : '');
|
||||
var queryString = qs.stringify(req.query);
|
||||
if(queryString.length) {
|
||||
category_url += '?' + queryString;
|
||||
}
|
||||
|
||||
data.privileges = privileges;
|
||||
data.currentPage = page;
|
||||
|
||||
// Paginator for noscript
|
||||
data.pages = [];
|
||||
for(var x=1;x<=data.pageCount;x++) {
|
||||
data.pages.push({
|
||||
page: x,
|
||||
active: x === parseInt(page, 10)
|
||||
});
|
||||
}
|
||||
|
||||
res.render('category', data);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = categoriesController;
|
||||
270
src/controllers/index.js
Normal file
270
src/controllers/index.js
Normal file
@@ -0,0 +1,270 @@
|
||||
"use strict";
|
||||
|
||||
var topicsController = require('./topics'),
|
||||
categoriesController = require('./categories'),
|
||||
usersController = require('./users'),
|
||||
accountsController = require('./accounts'),
|
||||
staticController = require('./static'),
|
||||
apiController = require('./api'),
|
||||
adminController = require('./admin'),
|
||||
|
||||
async = require('async'),
|
||||
nconf = require('nconf'),
|
||||
auth = require('./../routes/authentication'),
|
||||
meta = require('./../meta'),
|
||||
user = require('./../user'),
|
||||
posts = require('./../posts'),
|
||||
topics = require('./../topics'),
|
||||
plugins = require('./../plugins'),
|
||||
categories = require('./../categories'),
|
||||
categoryTools = require('./../categoryTools');
|
||||
|
||||
|
||||
|
||||
var Controllers = {
|
||||
topics: topicsController,
|
||||
categories: categoriesController,
|
||||
users: usersController,
|
||||
accounts: accountsController,
|
||||
static: staticController,
|
||||
api: apiController,
|
||||
admin: adminController
|
||||
};
|
||||
|
||||
|
||||
Controllers.home = function(req, res, next) {
|
||||
async.parallel({
|
||||
header: function (next) {
|
||||
res.locals.metaTags = [{
|
||||
name: "title",
|
||||
content: meta.config.title || 'NodeBB'
|
||||
}, {
|
||||
name: "description",
|
||||
content: meta.config.description || ''
|
||||
}, {
|
||||
property: 'og:title',
|
||||
content: 'Index | ' + (meta.config.title || 'NodeBB')
|
||||
}, {
|
||||
property: "og:type",
|
||||
content: 'website'
|
||||
}];
|
||||
|
||||
next(null);
|
||||
},
|
||||
categories: function (next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
categories.getAllCategories(uid, function (err, data) {
|
||||
data.categories = data.categories.filter(function (category) {
|
||||
return !category.disabled;
|
||||
});
|
||||
|
||||
function canSee(category, next) {
|
||||
categoryTools.privileges(category.cid, ((req.user) ? req.user.uid || 0 : 0), function(err, privileges) {
|
||||
next(!err && privileges.read);
|
||||
});
|
||||
}
|
||||
|
||||
function getRecentReplies(category, callback) {
|
||||
categories.getRecentReplies(category.cid, uid, parseInt(category.numRecentReplies, 10), function (err, posts) {
|
||||
category.posts = posts;
|
||||
category.post_count = posts.length > 2 ? 2 : posts.length; // this was a hack to make metro work back in the day, post_count should just = length
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
async.filter(data.categories, canSee, function(visibleCategories) {
|
||||
data.categories = visibleCategories;
|
||||
|
||||
async.each(data.categories, getRecentReplies, function (err) {
|
||||
next(err, data.categories);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}, function (err, data) {
|
||||
res.render('home', data);
|
||||
});
|
||||
};
|
||||
|
||||
Controllers.search = function(req, res, next) {
|
||||
if (!req.params.term) {
|
||||
return res.render('search', {
|
||||
show_no_topics: 'hide',
|
||||
show_no_posts: 'hide',
|
||||
show_results: 'hide',
|
||||
search_query: '',
|
||||
posts: [],
|
||||
topics: []
|
||||
});
|
||||
}
|
||||
|
||||
if (!plugins.hasListeners('filter:search.query')) {
|
||||
return res.redirect('/404');
|
||||
}
|
||||
|
||||
function searchPosts(callback) {
|
||||
plugins.fireHook('filter:search.query', {
|
||||
index: 'post',
|
||||
query: req.params.term
|
||||
}, function(err, pids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
posts.getPostSummaryByPids(pids, false, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function searchTopics(callback) {
|
||||
plugins.fireHook('filter:search.query', {
|
||||
index: 'topic',
|
||||
query: req.params.term
|
||||
}, function(err, tids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
topics.getTopicsByTids(tids, 0, callback);
|
||||
});
|
||||
}
|
||||
|
||||
async.parallel([searchPosts, searchTopics], function (err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!results) {
|
||||
results = [];
|
||||
results[0] = results[1] = [];
|
||||
}
|
||||
|
||||
return res.render('search', {
|
||||
show_no_topics: results[1].length ? 'hide' : '',
|
||||
show_no_posts: results[0].length ? 'hide' : '',
|
||||
show_results: '',
|
||||
search_query: req.params.term,
|
||||
posts: results[0],
|
||||
topics: results[1],
|
||||
post_matches : results[0].length,
|
||||
topic_matches : results[1].length
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Controllers.reset = function(req, res, next) {
|
||||
res.render('reset', {
|
||||
reset_code: req.params.code ? req.params.code : null
|
||||
});
|
||||
};
|
||||
|
||||
Controllers.login = function(req, res, next) {
|
||||
var data = {},
|
||||
login_strategies = auth.get_login_strategies(),
|
||||
num_strategies = login_strategies.length,
|
||||
emailersPresent = plugins.hasListeners('action:email.send');
|
||||
|
||||
if (num_strategies === 0) {
|
||||
data = {
|
||||
'login_window:spansize': 'col-md-12',
|
||||
'alternate_logins': false
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
'login_window:spansize': 'col-md-6',
|
||||
'alternate_logins': true
|
||||
};
|
||||
}
|
||||
|
||||
data.authentication = login_strategies;
|
||||
data.token = res.locals.csrf_token;
|
||||
data.showResetLink = emailersPresent;
|
||||
|
||||
res.render('login', data);
|
||||
};
|
||||
|
||||
Controllers.register = function(req, res, next) {
|
||||
var data = {},
|
||||
login_strategies = auth.get_login_strategies(),
|
||||
num_strategies = login_strategies.length;
|
||||
|
||||
if (num_strategies === 0) {
|
||||
data = {
|
||||
'register_window:spansize': 'col-md-12',
|
||||
'alternate_logins': false
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
'register_window:spansize': 'col-md-6',
|
||||
'alternate_logins': true
|
||||
};
|
||||
}
|
||||
|
||||
data.authentication = login_strategies;
|
||||
|
||||
data.token = res.locals.csrf_token;
|
||||
data.minimumUsernameLength = meta.config.minimumUsernameLength;
|
||||
data.maximumUsernameLength = meta.config.maximumUsernameLength;
|
||||
data.minimumPasswordLength = meta.config.minimumPasswordLength;
|
||||
data.termsOfUse = meta.config.termsOfUse;
|
||||
|
||||
res.render('register', data);
|
||||
};
|
||||
|
||||
|
||||
Controllers.confirmEmail = function(req, res, next) {
|
||||
user.email.confirm(req.params.code, function (data) {
|
||||
if (data.status === 'ok') {
|
||||
data = {
|
||||
'alert-class': 'alert-success',
|
||||
title: 'Email Confirmed',
|
||||
text: 'Thank you for vaidating your email. Your account is now fully activated.'
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
'alert-class': 'alert-danger',
|
||||
title: 'An error occurred...',
|
||||
text: 'There was a problem validating your email address. Perhaps the code was invalid or has expired.'
|
||||
};
|
||||
}
|
||||
|
||||
res.render('confirm', data);
|
||||
});
|
||||
};
|
||||
|
||||
Controllers.sitemap = function(req, res, next) {
|
||||
var sitemap = require('./../sitemap.js');
|
||||
|
||||
sitemap.render(function(xml) {
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send(xml);
|
||||
});
|
||||
};
|
||||
|
||||
Controllers.robots = function (req, res) {
|
||||
res.set('Content-Type', 'text/plain');
|
||||
|
||||
if (meta.config["robots.txt"]) {
|
||||
res.send(meta.config["robots.txt"]);
|
||||
} else {
|
||||
res.send("User-agent: *\n" +
|
||||
"Disallow: /admin/\n" +
|
||||
"Sitemap: " + nconf.get('url') + "/sitemap.xml");
|
||||
}
|
||||
};
|
||||
|
||||
Controllers.outgoing = function(req, res, next) {
|
||||
var url = req.query.url,
|
||||
data = {
|
||||
url: url,
|
||||
title: meta.config.title
|
||||
};
|
||||
|
||||
if (url) {
|
||||
res.render('outgoing', data);
|
||||
} else {
|
||||
res.status(404);
|
||||
res.redirect(nconf.get('relative_path') + '/404');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Controllers;
|
||||
17
src/controllers/static.js
Normal file
17
src/controllers/static.js
Normal file
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
var staticController = {};
|
||||
|
||||
staticController['404'] = function(req, res, next) {
|
||||
res.render('404', {});
|
||||
};
|
||||
|
||||
staticController['403'] = function(req, res, next) {
|
||||
res.render('403', {});
|
||||
};
|
||||
|
||||
staticController['500'] = function(req, res, next) {
|
||||
res.render('500', {});
|
||||
};
|
||||
|
||||
module.exports = staticController;
|
||||
184
src/controllers/topics.js
Normal file
184
src/controllers/topics.js
Normal file
@@ -0,0 +1,184 @@
|
||||
"use strict";
|
||||
|
||||
var topicsController = {},
|
||||
async = require('async'),
|
||||
S = require('string'),
|
||||
validator = require('validator'),
|
||||
nconf = require('nconf'),
|
||||
qs = require('querystring'),
|
||||
user = require('./../user'),
|
||||
meta = require('./../meta'),
|
||||
topics = require('./../topics'),
|
||||
threadTools = require('./../threadTools'),
|
||||
utils = require('./../../public/src/utils');
|
||||
|
||||
topicsController.get = function(req, res, next) {
|
||||
var tid = req.params.topic_id,
|
||||
page = req.query.page || 1,
|
||||
uid = req.user ? req.user.uid : 0,
|
||||
privileges;
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
threadTools.privileges(tid, ((req.user) ? req.user.uid || 0 : 0), function(err, userPrivileges) {
|
||||
if (!err) {
|
||||
if (!userPrivileges.read) {
|
||||
next(new Error('not-enough-privileges'));
|
||||
} else {
|
||||
privileges = userPrivileges;
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
user.getSettings(uid, function(err, settings) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var start = (page - 1) * settings.topicsPerPage,
|
||||
end = start + settings.topicsPerPage - 1;
|
||||
|
||||
topics.getTopicWithPosts(tid, uid, start, end, function (err, topicData) {
|
||||
if (topicData) {
|
||||
if (parseInt(topicData.deleted, 10) === 1 && parseInt(topicData.expose_tools, 10) === 0) {
|
||||
return next(new Error('Topic deleted'), null);
|
||||
}
|
||||
}
|
||||
|
||||
next(err, topicData);
|
||||
});
|
||||
});
|
||||
},
|
||||
function (topicData, next) {
|
||||
var lastMod = topicData.timestamp,
|
||||
description = (function() {
|
||||
var content = '';
|
||||
if(topicData.posts.length) {
|
||||
content = S(topicData.posts[0].content).stripTags().s;
|
||||
}
|
||||
|
||||
if (content.length > 255) {
|
||||
content = content.substr(0, 255) + '...';
|
||||
}
|
||||
|
||||
return validator.escape(content);
|
||||
})(),
|
||||
timestamp;
|
||||
|
||||
for (var x = 0, numPosts = topicData.posts.length; x < numPosts; x++) {
|
||||
timestamp = parseInt(topicData.posts[x].timestamp, 10);
|
||||
if (timestamp > lastMod) {
|
||||
lastMod = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
var ogImageUrl = meta.config['brand:logo'];
|
||||
if(ogImageUrl && ogImageUrl.indexOf('http') === -1) {
|
||||
ogImageUrl = nconf.get('url') + ogImageUrl;
|
||||
}
|
||||
|
||||
res.locals.metaTags = [
|
||||
{
|
||||
name: "title",
|
||||
content: topicData.title
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
content: description
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: topicData.title
|
||||
},
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description
|
||||
},
|
||||
{
|
||||
property: "og:type",
|
||||
content: 'article'
|
||||
},
|
||||
{
|
||||
property: "og:url",
|
||||
content: nconf.get('url') + '/topic/' + topicData.slug
|
||||
},
|
||||
{
|
||||
property: "og:image:url",
|
||||
content: ogImageUrl
|
||||
},
|
||||
{
|
||||
property: 'og:image',
|
||||
content: topicData.posts.length?topicData.posts[0].picture:''
|
||||
},
|
||||
{
|
||||
property: "article:published_time",
|
||||
content: utils.toISOString(topicData.timestamp)
|
||||
},
|
||||
{
|
||||
property: 'article:modified_time',
|
||||
content: utils.toISOString(lastMod)
|
||||
},
|
||||
{
|
||||
property: 'article:section',
|
||||
content: topicData.category.name
|
||||
}
|
||||
];
|
||||
|
||||
res.locals.linkTags = [
|
||||
{
|
||||
rel: 'alternate',
|
||||
type: 'application/rss+xml',
|
||||
href: nconf.get('url') + '/topic/' + tid + '.rss'
|
||||
},
|
||||
{
|
||||
rel: 'up',
|
||||
href: nconf.get('url') + '/category/' + topicData.category.slug
|
||||
}
|
||||
];
|
||||
|
||||
next(null, topicData);
|
||||
}
|
||||
], function (err, data) {
|
||||
if (err) {
|
||||
if (err.message === 'not-enough-privileges') {
|
||||
return res.redirect('403');
|
||||
} else {
|
||||
return res.redirect('404');
|
||||
}
|
||||
}
|
||||
|
||||
data.privileges = privileges;
|
||||
|
||||
var topic_url = tid + (req.params.slug ? '/' + req.params.slug : '');
|
||||
var queryString = qs.stringify(req.query);
|
||||
if(queryString.length) {
|
||||
topic_url += '?' + queryString;
|
||||
}
|
||||
|
||||
|
||||
if (uid) {
|
||||
topics.markAsRead(tid, uid, function(err) {
|
||||
topics.pushUnreadCount(uid);
|
||||
});
|
||||
}
|
||||
|
||||
topics.increaseViewCount(tid);
|
||||
|
||||
// Paginator for noscript
|
||||
data.pages = [];
|
||||
for(var x=1;x<=data.pageCount;x++) {
|
||||
data.pages.push({
|
||||
page: x,
|
||||
active: x === parseInt(page, 10)
|
||||
});
|
||||
}
|
||||
|
||||
res.render('topic', data);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = topicsController;
|
||||
114
src/controllers/users.js
Normal file
114
src/controllers/users.js
Normal file
@@ -0,0 +1,114 @@
|
||||
"use strict";
|
||||
|
||||
var usersController = {};
|
||||
|
||||
var async = require('async'),
|
||||
user = require('./../user'),
|
||||
db = require('./../database');
|
||||
|
||||
usersController.getOnlineUsers = function(req, res, next) {
|
||||
var websockets = require('../socket.io');
|
||||
|
||||
user.getUsers('users:online', 0, 49, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
var onlineUsers = [],
|
||||
uid = 0;
|
||||
|
||||
if (req.user) {
|
||||
uid = req.user.uid;
|
||||
}
|
||||
|
||||
user.isAdministrator(uid, function (err, isAdministrator) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!isAdministrator) {
|
||||
data = data.filter(function(item) {
|
||||
return item.status !== 'offline';
|
||||
});
|
||||
}
|
||||
|
||||
function iterator(userData, next) {
|
||||
var online = websockets.isUserOnline(userData.uid);
|
||||
if(!online) {
|
||||
db.sortedSetRemove('users:online', userData.uid);
|
||||
return next(null);
|
||||
}
|
||||
|
||||
onlineUsers.push(userData);
|
||||
next(null);
|
||||
}
|
||||
|
||||
var anonymousUserCount = websockets.getOnlineAnonCount();
|
||||
|
||||
async.each(data, iterator, function(err) {
|
||||
var userData = {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: onlineUsers,
|
||||
anonymousUserCount: anonymousUserCount,
|
||||
show_anon: anonymousUserCount?'':'hide'
|
||||
};
|
||||
|
||||
res.render('users', userData);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
usersController.getUsersSortedByPosts = function(req, res, next) {
|
||||
user.getUsers('users:postcount', 0, 49, function (err, data) {
|
||||
var userData = {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: data,
|
||||
show_anon: 'hide'
|
||||
};
|
||||
|
||||
res.render('users', userData);
|
||||
});
|
||||
};
|
||||
|
||||
usersController.getUsersSortedByReputation = function(req, res, next) {
|
||||
user.getUsers('users:reputation', 0, 49, function (err, data) {
|
||||
var userData = {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: data,
|
||||
show_anon: 'hide'
|
||||
};
|
||||
|
||||
res.render('users', userData);
|
||||
});
|
||||
};
|
||||
|
||||
usersController.getUsersSortedByJoinDate = function(req, res, next) {
|
||||
user.getUsers('users:joindate', 0, 49, function (err, data) {
|
||||
var userData = {
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: data,
|
||||
show_anon: 'hide'
|
||||
};
|
||||
|
||||
res.render('users', userData);
|
||||
});
|
||||
};
|
||||
|
||||
usersController.getUsersForSearch = function(req, res, next) {
|
||||
var data = {
|
||||
search_display: 'block',
|
||||
loadmore_display: 'none',
|
||||
users: [],
|
||||
show_anon: 'hide'
|
||||
};
|
||||
|
||||
res.render('users', data);
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = usersController;
|
||||
17
src/meta.js
17
src/meta.js
@@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
async = require('async'),
|
||||
@@ -105,8 +107,9 @@ var fs = require('fs'),
|
||||
|
||||
if (fs.existsSync(config)) {
|
||||
fs.readFile(config, function (err, file) {
|
||||
if (err) return next();
|
||||
else {
|
||||
if (err) {
|
||||
return next();
|
||||
} else {
|
||||
var configObj = JSON.parse(file.toString());
|
||||
next(err, configObj);
|
||||
}
|
||||
@@ -153,12 +156,12 @@ var fs = require('fs'),
|
||||
db.setObject('config', themeData, next);
|
||||
}
|
||||
], callback);
|
||||
break;
|
||||
break;
|
||||
|
||||
case 'bootswatch':
|
||||
themeData['theme:src'] = data.src;
|
||||
db.setObject('config', themeData, callback);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -213,7 +216,7 @@ var fs = require('fs'),
|
||||
if (subpage) {
|
||||
translator.translate('[[pages:user.' + subpage + ', ' + username + ']]', function(translated) {
|
||||
callback(null, translated);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
callback(null, username);
|
||||
}
|
||||
@@ -277,7 +280,9 @@ var fs = require('fs'),
|
||||
});
|
||||
|
||||
// Remove scripts that could not be found (remove this line at v0.5.0)
|
||||
Meta.js.scripts = jsPaths.filter(function(path) { return path !== null });
|
||||
Meta.js.scripts = jsPaths.filter(function(path) {
|
||||
return path !== null;
|
||||
});
|
||||
|
||||
// Add plugin scripts
|
||||
Meta.js.scripts = Meta.js.scripts.concat(plugins.clientScripts);
|
||||
|
||||
76
src/middleware/admin.js
Normal file
76
src/middleware/admin.js
Normal file
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
|
||||
var app,
|
||||
middleware = {},
|
||||
nconf = require('nconf'),
|
||||
async = require('async'),
|
||||
user = require('./../user'),
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins'),
|
||||
|
||||
controllers = {
|
||||
api: require('./../controllers/api')
|
||||
};
|
||||
|
||||
|
||||
middleware.isAdmin = function(req, res, next) {
|
||||
user.isAdministrator((req.user && req.user.uid) ? req.user.uid : 0, function (err, isAdmin) {
|
||||
if (!isAdmin) {
|
||||
res.status(403);
|
||||
res.redirect('/403');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
middleware.buildHeader = function(req, res, next) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
var custom_header = {
|
||||
'plugins': [],
|
||||
'authentication': []
|
||||
};
|
||||
|
||||
user.getUserFields(req.user.uid, ['username', 'userslug', 'picture'], function(err, userData) {
|
||||
plugins.fireHook('filter:admin.header.build', custom_header, function(err, custom_header) {
|
||||
var data = {
|
||||
csrf: res.locals.csrf_token,
|
||||
relative_path: nconf.get('relative_path'),
|
||||
plugins: custom_header.plugins,
|
||||
authentication: custom_header.authentication,
|
||||
userpicture: userData.picture,
|
||||
username: userData.username,
|
||||
userslug: userData.userslug,
|
||||
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
|
||||
env: process.env.NODE_ENV ? true : false
|
||||
};
|
||||
|
||||
app.render('admin/header', data, function(err, template) {
|
||||
res.locals.adminHeader = template;
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
controllers.api.getConfig(req, res, function(err, config) {
|
||||
res.locals.config = config;
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
app.render('admin/footer', {}, function(err, template) {
|
||||
res.locals.adminFooter = template;
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function(webserver) {
|
||||
app = webserver;
|
||||
return middleware;
|
||||
};
|
||||
235
src/middleware/index.js
Normal file
235
src/middleware/index.js
Normal file
@@ -0,0 +1,235 @@
|
||||
"use strict";
|
||||
|
||||
var templates = require('./../../public/src/templates'),
|
||||
translator = require('./../../public/src/translator'),
|
||||
utils = require('./../../public/src/utils'),
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins'),
|
||||
db = require('./../database'),
|
||||
auth = require('./../routes/authentication'),
|
||||
async = require('async'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
nconf = require('nconf'),
|
||||
express = require('express'),
|
||||
winston = require('winston');
|
||||
|
||||
|
||||
var middleware = {};
|
||||
|
||||
|
||||
function routeThemeScreenshots(app, themes) {
|
||||
var screenshotPath;
|
||||
|
||||
async.each(themes, function(themeObj, next) {
|
||||
if (themeObj.screenshot) {
|
||||
screenshotPath = path.join(nconf.get('themes_path'), themeObj.id, themeObj.screenshot);
|
||||
(function(id, path) {
|
||||
fs.exists(path, function(exists) {
|
||||
if (exists) {
|
||||
app.get('/css/previews/' + id, function(req, res) {
|
||||
res.sendfile(path);
|
||||
});
|
||||
}
|
||||
});
|
||||
})(themeObj.id, screenshotPath);
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function routeCurrentTheme(app, themeData) {
|
||||
var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla');
|
||||
|
||||
// Detect if a theme has been selected, and handle appropriately
|
||||
if (!themeData['theme:type'] || themeData['theme:type'] === 'local') {
|
||||
// Local theme
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[themes] Using theme ' + themeId);
|
||||
}
|
||||
|
||||
// Theme's static directory
|
||||
if (themeData['theme:staticDir']) {
|
||||
app.use(nconf.get('relative_path') + '/css/assets', express.static(path.join(nconf.get('themes_path'), themeData['theme:id'], themeData['theme:staticDir']), {
|
||||
maxAge: app.enabled('cache') ? 5184000000 : 0
|
||||
}));
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('Static directory routed for theme: ' + themeData['theme:id']);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If not using a local theme (bootswatch, etc), drop back to vanilla
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[themes] Using theme ' + themeId);
|
||||
}
|
||||
|
||||
app.use(require('less-middleware')({
|
||||
src: path.join(nconf.get('themes_path'), '/nodebb-theme-vanilla'),
|
||||
dest: path.join(__dirname, '../../public/css'),
|
||||
prefix: nconf.get('relative_path') + '/css',
|
||||
yuicompress: app.enabled('minification') ? true : false
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function compileTemplates(pluginTemplates) {
|
||||
var mkdirp = require('mkdirp'),
|
||||
rimraf = require('rimraf');
|
||||
|
||||
winston.info('[themes] Compiling templates');
|
||||
rimraf.sync(nconf.get('views_dir'));
|
||||
mkdirp.sync(nconf.get('views_dir'));
|
||||
|
||||
utils.walk(nconf.get('base_templates_path'), function(err, baseTpls) {
|
||||
utils.walk(nconf.get('theme_templates_path'), function (err, themeTpls) {
|
||||
var paths = pluginTemplates;
|
||||
|
||||
if (!baseTpls || !themeTpls) {
|
||||
winston.warn('[themes] Could not find base template files at: ' + nconf.get('base_templates_path'));
|
||||
}
|
||||
|
||||
baseTpls = !baseTpls ? [] : baseTpls.map(function(tpl) { return tpl.replace(nconf.get('base_templates_path'), ''); });
|
||||
themeTpls = !themeTpls ? [] : themeTpls.map(function(tpl) { return tpl.replace(nconf.get('theme_templates_path'), ''); });
|
||||
|
||||
baseTpls.forEach(function(el, i) {
|
||||
var relative_path = (themeTpls.indexOf(el) !== -1 ? themeTpls[themeTpls.indexOf(el)] : baseTpls[i]),
|
||||
full_path = path.join(themeTpls.indexOf(el) !== -1 ? nconf.get('theme_templates_path') : nconf.get('base_templates_path'), relative_path);
|
||||
|
||||
paths[themeTpls.indexOf(el) !== -1 ? themeTpls[themeTpls.indexOf(el)] : baseTpls[i]] = full_path;
|
||||
});
|
||||
|
||||
async.each(Object.keys(paths), function(relative_path, next) {
|
||||
var file = fs.readFileSync(paths[relative_path]).toString(),
|
||||
matches = null,
|
||||
regex = /[ \t]*<!-- IMPORT ([\s\S]*?)? -->[ \t]*/;
|
||||
|
||||
while (matches = file.match(regex)) {
|
||||
if (paths["/" + matches[1]]) {
|
||||
file = file.replace(regex, fs.readFileSync(paths["/" + matches[1]]).toString());
|
||||
} else {
|
||||
winston.warn('[themes] Partial not found: ' + matches[1]);
|
||||
file = file.replace(regex, "");
|
||||
}
|
||||
}
|
||||
|
||||
mkdirp.sync(path.join(nconf.get('views_dir'), relative_path.split('/').slice(0, -1).join('/')));
|
||||
fs.writeFile(path.join(nconf.get('views_dir'), relative_path), file, next);
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
} else {
|
||||
winston.info('[themes] Successfully compiled templates.');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleErrors(err, req, res, next) {
|
||||
// we may use properties of the error object
|
||||
// here and next(err) appropriately, or if
|
||||
// we possibly recovered from the error, simply next().
|
||||
console.error(err.stack);
|
||||
var status = err.status || 500;
|
||||
res.status(status);
|
||||
|
||||
res.json(status, {
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
function catch404(req, res, next) {
|
||||
var isLanguage = new RegExp('^' + nconf.get('relative_path') + '/language/[\\w]{2,}/.*.json'),
|
||||
isClientScript = new RegExp('^' + nconf.get('relative_path') + '\\/src\\/forum(\\/admin)?\\/[\\w]+\\.js');
|
||||
|
||||
res.status(404);
|
||||
|
||||
if (isClientScript.test(req.url)) {
|
||||
res.type('text/javascript').send(200, '');
|
||||
} else if (isLanguage.test(req.url)) {
|
||||
res.json(200, {});
|
||||
} else if (req.accepts('html')) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.warn('Route requested but not found: ' + req.url);
|
||||
}
|
||||
|
||||
res.redirect(nconf.get('relative_path') + '/404');
|
||||
} else if (req.accepts('json')) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.warn('Route requested but not found: ' + req.url);
|
||||
}
|
||||
|
||||
res.json({
|
||||
error: 'Not found'
|
||||
});
|
||||
} else {
|
||||
res.type('txt').send('Not found');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = function(app, data) {
|
||||
middleware = require('./middleware')(app);
|
||||
|
||||
app.configure(function() {
|
||||
app.engine('tpl', templates.__express);
|
||||
app.set('view engine', 'tpl');
|
||||
app.set('views', nconf.get('views_dir'));
|
||||
|
||||
app.use(express.compress());
|
||||
|
||||
app.use(express.favicon(path.join(__dirname, '../../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico')));
|
||||
app.use('/apple-touch-icon', middleware.routeTouchIcon);
|
||||
|
||||
app.use(require('less-middleware')({
|
||||
src: path.join(__dirname, '../../', 'public'),
|
||||
prefix: nconf.get('relative_path'),
|
||||
yuicompress: app.enabled('minification') ? true : false
|
||||
}));
|
||||
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.cookieParser());
|
||||
|
||||
app.use(express.session({
|
||||
store: db.sessionStore,
|
||||
secret: nconf.get('secret'),
|
||||
key: 'express.sid',
|
||||
cookie: {
|
||||
maxAge: 1000 * 60 * 60 * 24 * parseInt(meta.configs.loginDays || 14, 10)
|
||||
}
|
||||
}));
|
||||
|
||||
app.use(express.csrf()); // todo, make this a conditional middleware
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
res.locals.csrf_token = req.session._csrf;
|
||||
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(middleware.processRender);
|
||||
|
||||
auth.initialize(app);
|
||||
|
||||
routeCurrentTheme(app, data.currentThemeData);
|
||||
routeThemeScreenshots(app, data.themesData);
|
||||
|
||||
plugins.getTemplates(function(err, pluginTemplates) {
|
||||
compileTemplates(pluginTemplates);
|
||||
});
|
||||
|
||||
app.use(app.router);
|
||||
|
||||
app.use(nconf.get('relative_path'), express.static(path.join(__dirname, '../../', 'public'), {
|
||||
maxAge: app.enabled('cache') ? 5184000000 : 0
|
||||
}));
|
||||
|
||||
app.use(catch404);
|
||||
app.use(handleErrors);
|
||||
});
|
||||
|
||||
return middleware;
|
||||
};
|
||||
340
src/middleware/middleware.js
Normal file
340
src/middleware/middleware.js
Normal file
@@ -0,0 +1,340 @@
|
||||
"use strict";
|
||||
|
||||
var app,
|
||||
clientScripts,
|
||||
middleware = {},
|
||||
async = require('async'),
|
||||
path = require('path'),
|
||||
winston = require('winston'),
|
||||
validator = require('validator'),
|
||||
fs = require('fs'),
|
||||
nconf = require('nconf'),
|
||||
plugins = require('./../plugins'),
|
||||
meta = require('./../meta'),
|
||||
translator = require('./../../public/src/translator'),
|
||||
user = require('./../user'),
|
||||
db = require('./../database'),
|
||||
|
||||
controllers = {
|
||||
api: require('./../controllers/api')
|
||||
};
|
||||
|
||||
middleware.authenticate = function(req, res, next) {
|
||||
if(!req.user) {
|
||||
if (res.locals.isAPI) {
|
||||
return res.json(403, 'not-allowed');
|
||||
} else {
|
||||
return res.redirect('403');
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
middleware.updateLastOnlineTime = function(req, res, next) {
|
||||
if(req.user) {
|
||||
user.updateLastOnlineTime(req.user.uid);
|
||||
}
|
||||
|
||||
db.sortedSetAdd('ip:recent', Date.now(), req.ip || 'Unknown');
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.prepareAPI = function(req, res, next) {
|
||||
res.locals.isAPI = true;
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.guestSearchingAllowed = function(req, res, next) {
|
||||
if (!req.user && meta.config.allowGuestSearching !== '1') {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.checkGlobalPrivacySettings = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
if (!callerUID && !!parseInt(meta.config.privateUserInfo, 10)) {
|
||||
if (res.locals.isAPI) {
|
||||
return res.json(403, 'not-allowed');
|
||||
} else {
|
||||
return res.redirect('403');
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.checkAccountPermissions = function(req, res, next) {
|
||||
var callerUID = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
// this function requires userslug to be passed in. todo: /user/uploadpicture should pass in userslug I think
|
||||
user.getUidByUserslug(req.params.userslug, function (err, uid) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// not sure if this check really should belong here. also make sure we're not doing this check again in the actual method
|
||||
if (!uid) {
|
||||
if (res.locals.isAPI) {
|
||||
return res.json(404);
|
||||
} else {
|
||||
return res.redirect('404');
|
||||
}
|
||||
}
|
||||
|
||||
if (parseInt(uid, 10) === callerUID) {
|
||||
return next();
|
||||
}
|
||||
|
||||
user.isAdministrator(callerUID, function(err, isAdmin) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(isAdmin) {
|
||||
next();
|
||||
}
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
return res.json(403, 'not-allowed');
|
||||
} else {
|
||||
return res.redirect('403');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
middleware.buildHeader = function(req, res, next) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
res.locals.renderHeader = true;
|
||||
next();
|
||||
},
|
||||
function(next) {
|
||||
controllers.api.getConfig(req, res, function(err, config) {
|
||||
res.locals.config = config;
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
// this is slower than the original implementation because the rendered template is not cached
|
||||
// but I didn't bother to fix this because we will deprecate [filter:footer.build] in favour of the widgets system by 0.4x
|
||||
plugins.fireHook('filter:footer.build', '', function(err, appendHTML) {
|
||||
app.render('footer', {footerHTML: appendHTML}, function(err, template) {
|
||||
translator.translate(template, function(parsedTemplate) {
|
||||
res.locals.footer = template;
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
|
||||
middleware.renderHeader = function(req, res, callback) {
|
||||
var custom_header = {
|
||||
'navigation': []
|
||||
};
|
||||
|
||||
plugins.fireHook('filter:header.build', custom_header, function(err, custom_header) {
|
||||
var defaultMetaTags = [{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1.0, user-scalable=no'
|
||||
}, {
|
||||
name: 'content-type',
|
||||
content: 'text/html; charset=UTF-8'
|
||||
}, {
|
||||
name: 'apple-mobile-web-app-capable',
|
||||
content: 'yes'
|
||||
}, {
|
||||
property: 'og:site_name',
|
||||
content: meta.config.title || 'NodeBB'
|
||||
}, {
|
||||
property: 'keywords',
|
||||
content: meta.config.keywords || ''
|
||||
}],
|
||||
defaultLinkTags = [{
|
||||
rel: 'apple-touch-icon',
|
||||
href: '/apple-touch-icon'
|
||||
}],
|
||||
templateValues = {
|
||||
bootswatchCSS: meta.config['theme:src'],
|
||||
title: meta.config.title || '',
|
||||
description: meta.config.description || '',
|
||||
'brand:logo': meta.config['brand:logo'] || '',
|
||||
'brand:logo:display': meta.config['brand:logo']?'':'hide',
|
||||
csrf: res.locals.csrf_token,
|
||||
relative_path: nconf.get('relative_path'),
|
||||
clientScripts: clientScripts,
|
||||
navigation: custom_header.navigation,
|
||||
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
|
||||
allowRegistration: meta.config.allowRegistration === undefined || parseInt(meta.config.allowRegistration, 10) === 1,
|
||||
searchEnabled: plugins.hasListeners('filter:search.query') ? true : false
|
||||
},
|
||||
escapeList = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
"'": ''',
|
||||
'"': '"'
|
||||
};
|
||||
|
||||
for (var key in res.locals.config) {
|
||||
if (res.locals.config.hasOwnProperty(key)) {
|
||||
templateValues[key] = res.locals.config[key];
|
||||
}
|
||||
}
|
||||
|
||||
var uid = '0';
|
||||
|
||||
templateValues.metaTags = defaultMetaTags.concat(res.locals.metaTags || []).map(function(tag) {
|
||||
if(!tag || typeof tag.content !== 'string') {
|
||||
winston.warn('Invalid meta tag. ', tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
tag.content = tag.content.replace(/[&<>'"]/g, function(tag) {
|
||||
return escapeList[tag] || tag;
|
||||
});
|
||||
return tag;
|
||||
});
|
||||
|
||||
templateValues.linkTags = defaultLinkTags.concat(res.locals.linkTags || []);
|
||||
templateValues.linkTags.push({
|
||||
rel: "icon",
|
||||
type: "image/x-icon",
|
||||
href: nconf.get('relative_path') + '/favicon.ico'
|
||||
});
|
||||
|
||||
if(req.user && req.user.uid) {
|
||||
uid = req.user.uid;
|
||||
}
|
||||
|
||||
templateValues.useCustomCSS = false;
|
||||
if (meta.config.useCustomCSS === '1') {
|
||||
templateValues.useCustomCSS = true;
|
||||
templateValues.customCSS = meta.config.customCSS;
|
||||
}
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
translator.get('pages:' + path.basename(req.url), function(translated) {
|
||||
var metaTitle = templateValues.metaTags.filter(function(tag) {
|
||||
return tag.name === 'title';
|
||||
});
|
||||
|
||||
if (translated) {
|
||||
templateValues.browserTitle = translated;
|
||||
} else if (metaTitle.length > 0 && metaTitle[0].content) {
|
||||
templateValues.browserTitle = metaTitle[0].content;
|
||||
} else {
|
||||
templateValues.browserTitle = meta.config.browserTitle || 'NodeBB';
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
user.isAdministrator(uid, function(err, isAdmin) {
|
||||
templateValues.isAdmin = isAdmin || false;
|
||||
next();
|
||||
});
|
||||
}
|
||||
], function() {
|
||||
app.render('header', templateValues, function(err, template) {
|
||||
callback(null, template);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
middleware.processRender = function(req, res, next) {
|
||||
// res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687
|
||||
var render = res.render;
|
||||
res.render = function(template, options, fn) {
|
||||
var self = this,
|
||||
options = options || {},
|
||||
req = this.req,
|
||||
app = req.app,
|
||||
defaultFn = function(err, str){
|
||||
if (err) {
|
||||
return req.next(err);
|
||||
}
|
||||
|
||||
self.send(str);
|
||||
};
|
||||
|
||||
if ('function' === typeof options) {
|
||||
fn = options, options = {};
|
||||
}
|
||||
|
||||
if ('function' !== typeof fn) {
|
||||
fn = defaultFn;
|
||||
}
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
return res.json(options);
|
||||
}
|
||||
|
||||
render.call(self, template, options, function(err, str) {
|
||||
if (res.locals.footer) {
|
||||
str = str + res.locals.footer;
|
||||
} else if (res.locals.adminFooter) {
|
||||
str = str + res.locals.adminFooter;
|
||||
}
|
||||
|
||||
if (res.locals.renderHeader) {
|
||||
middleware.renderHeader(req, res, function(err, template) {
|
||||
str = template + str;
|
||||
|
||||
translator.translate(str, function(translated) {
|
||||
fn(err, translated);
|
||||
});
|
||||
});
|
||||
} else if (res.locals.adminHeader) {
|
||||
str = res.locals.adminHeader + str;
|
||||
fn(err, str);
|
||||
} else {
|
||||
fn(err, str);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
middleware.routeTouchIcon = function(req, res) {
|
||||
if (meta.config['brand:logo'] && validator.isURL(meta.config['brand:logo'])) {
|
||||
return res.redirect(meta.config['brand:logo']);
|
||||
} else {
|
||||
return res.sendfile(path.join(__dirname, '../../public', meta.config['brand:logo'] || '/logo.png'), {
|
||||
maxAge: app.enabled('cache') ? 5184000000 : 0
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function(webserver) {
|
||||
app = webserver;
|
||||
middleware.admin = require('./admin')(webserver);
|
||||
|
||||
plugins.ready(function() {
|
||||
// Minify client-side libraries
|
||||
meta.js.get(function (err, scripts) {
|
||||
clientScripts = scripts.map(function (script) {
|
||||
script = {
|
||||
script: script
|
||||
};
|
||||
|
||||
return script;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return middleware;
|
||||
};
|
||||
@@ -8,6 +8,7 @@ var fs = require('fs'),
|
||||
|
||||
db = require('./database'),
|
||||
meta = require('./meta'),
|
||||
utils = require('./../public/src/utils'),
|
||||
pkg = require('../package.json');
|
||||
|
||||
(function(Plugins) {
|
||||
@@ -297,7 +298,14 @@ var fs = require('fs'),
|
||||
return (Plugins.loadedHooks[hook] && Plugins.loadedHooks[hook].length > 0);
|
||||
};
|
||||
|
||||
Plugins.fireHook = function(hook, args, callback) {
|
||||
Plugins.fireHook = function(hook) {
|
||||
var callback = typeof arguments[arguments.length-1] === "function" ? arguments[arguments.length-1] : null,
|
||||
args = arguments.length ? Array.prototype.slice.call(arguments, 1) : [];
|
||||
|
||||
if (callback) {
|
||||
args.pop();
|
||||
}
|
||||
|
||||
hookList = Plugins.loadedHooks[hook];
|
||||
|
||||
if (hookList && Array.isArray(hookList)) {
|
||||
@@ -307,32 +315,35 @@ var fs = require('fs'),
|
||||
case 'filter':
|
||||
async.reduce(hookList, args, function(value, hookObj, next) {
|
||||
if (hookObj.method) {
|
||||
if (hookObj.callbacked) { // If a callback is present (asynchronous method)
|
||||
hookObj.method.call(Plugins.libraries[hookObj.id], value, next);
|
||||
} else { // Synchronous method
|
||||
value = hookObj.method.call(Plugins.libraries[hookObj.id], value);
|
||||
next(null, value);
|
||||
if (hookObj.callbacked) {
|
||||
hookObj.method.apply(Plugins, value.concat(function() {
|
||||
next(arguments[0], Array.prototype.slice.call(arguments, 1));
|
||||
}));
|
||||
} else {
|
||||
winston.warn('[plugins] "callbacked" deprecated as of 0.4x. Use asynchronous method instead for hook: ' + hook);
|
||||
value = hookObj.method.apply(Plugins, value);
|
||||
next(null, [value]);
|
||||
}
|
||||
} else {
|
||||
if (global.env === 'development') {
|
||||
winston.info('[plugins] Expected method for hook \'' + hook + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
|
||||
}
|
||||
next(null, value);
|
||||
next(null, [value]);
|
||||
}
|
||||
}, function(err, value) {
|
||||
}, function(err, values) {
|
||||
if (err) {
|
||||
if (global.env === 'development') {
|
||||
winston.info('[plugins] Problem executing hook: ' + hook);
|
||||
}
|
||||
}
|
||||
|
||||
callback.apply(Plugins, arguments);
|
||||
callback.apply(Plugins, [err].concat(values));
|
||||
});
|
||||
break;
|
||||
case 'action':
|
||||
async.each(hookList, function(hookObj) {
|
||||
if (hookObj.method) {
|
||||
hookObj.method.call(Plugins.libraries[hookObj.id], args);
|
||||
hookObj.method.apply(Plugins, args);
|
||||
} else {
|
||||
if (global.env === 'development') {
|
||||
winston.info('[plugins] Expected method \'' + hookObj.method + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
|
||||
@@ -346,10 +357,11 @@ var fs = require('fs'),
|
||||
}
|
||||
} else {
|
||||
// Otherwise, this hook contains no methods
|
||||
var returnVal = args;
|
||||
if (callback) {
|
||||
callback(null, returnVal);
|
||||
callback.apply(this, [null].concat(args));
|
||||
}
|
||||
|
||||
return args[0];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -391,7 +403,30 @@ var fs = require('fs'),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Plugins.getTemplates = function(callback) {
|
||||
var templates = {};
|
||||
|
||||
Plugins.showInstalled(function(err, plugins) {
|
||||
async.each(plugins, function(plugin, next) {
|
||||
if (plugin.templates && plugin.id && plugin.active) {
|
||||
var templatesPath = path.join(__dirname, '../node_modules', plugin.id, plugin.templates);
|
||||
utils.walk(templatesPath, function(err, pluginTemplates) {
|
||||
pluginTemplates.forEach(function(pluginTemplate) {
|
||||
templates[pluginTemplate.replace(templatesPath, '').substring(1)] = pluginTemplate;
|
||||
});
|
||||
|
||||
next(err);
|
||||
});
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
}, function(err) {
|
||||
callback(err, templates);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Plugins.showInstalled = function(callback) {
|
||||
npmPluginPath = path.join(__dirname, '../node_modules');
|
||||
@@ -459,5 +494,5 @@ var fs = require('fs'),
|
||||
], function(err, plugins) {
|
||||
callback(err, plugins);
|
||||
});
|
||||
}
|
||||
};
|
||||
}(exports));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var nconf = require('nconf'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
@@ -10,446 +12,186 @@ var nconf = require('nconf'),
|
||||
topics = require('./../topics'),
|
||||
pkg = require('./../../package'),
|
||||
categories = require('./../categories'),
|
||||
meta = require('../meta'),
|
||||
plugins = require('../plugins'),
|
||||
widgets = require('../widgets'),
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins'),
|
||||
widgets = require('./../widgets'),
|
||||
image = require('./../image'),
|
||||
file = require('./../file'),
|
||||
Languages = require('../languages'),
|
||||
Languages = require('./../languages'),
|
||||
events = require('./../events'),
|
||||
utils = require('./../../public/src/utils'),
|
||||
templates = require('./../../public/src/templates');
|
||||
|
||||
(function (Admin) {
|
||||
Admin.isAdmin = function (req, res, next) {
|
||||
user.isAdministrator((req.user && req.user.uid) ? req.user.uid : 0, function (err, isAdmin) {
|
||||
if (!isAdmin) {
|
||||
res.status(403);
|
||||
res.redirect('/403');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Admin.buildHeader = function (req, res, callback) {
|
||||
var custom_header = {
|
||||
'plugins': [],
|
||||
'authentication': []
|
||||
};
|
||||
function uploadImage(filename, req, res) {
|
||||
function done(err, image) {
|
||||
var er, rs;
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
|
||||
user.getUserFields(req.user.uid, ['username', 'userslug', 'picture'], function(err, userData) {
|
||||
plugins.fireHook('filter:admin.header.build', custom_header, function(err, custom_header) {
|
||||
callback(err, templates['admin/header'].parse({
|
||||
csrf: res.locals.csrf_token,
|
||||
relative_path: nconf.get('relative_path'),
|
||||
plugins: custom_header.plugins,
|
||||
authentication: custom_header.authentication,
|
||||
userpicture: userData.picture,
|
||||
username: userData.username,
|
||||
userslug: userData.userslug,
|
||||
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
|
||||
env: process.env.NODE_ENV ? true : false
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Admin.createRoutes = function (app) {
|
||||
app.all('/api/admin/*', Admin.isAdmin);
|
||||
app.all('/admin/*', Admin.isAdmin);
|
||||
app.get('/admin', Admin.isAdmin);
|
||||
|
||||
(function () {
|
||||
var routes = [
|
||||
'categories/active', 'categories/disabled', 'users', 'topics', 'settings', 'themes',
|
||||
'database', 'events', 'motd', 'groups', 'plugins', 'languages', 'logger',
|
||||
'users/latest', 'users/sort-posts', 'users/sort-reputation', 'users/search'
|
||||
];
|
||||
|
||||
for (var i = 0, ii = routes.length; i < ii; i++) {
|
||||
(function (route) {
|
||||
app.get('/admin/' + route, function (req, res) {
|
||||
Admin.buildHeader(req, res, function(err, header) {
|
||||
res.send(header + app.create_route('admin/' + route) + templates['admin/footer']);
|
||||
});
|
||||
});
|
||||
}(routes[i]));
|
||||
}
|
||||
|
||||
var unit_tests = ['categories'];
|
||||
|
||||
for (var i = 0, ii = unit_tests.length; i < ii; i++) {
|
||||
(function (route) {
|
||||
app.get('/admin/testing/' + route, function (req, res) {
|
||||
Admin.buildHeader(req, res, function(err, header) {
|
||||
res.send(header + app.create_route('admin/testing/' + route) + templates['admin/footer']);
|
||||
});
|
||||
});
|
||||
}(unit_tests[i]));
|
||||
}
|
||||
|
||||
}());
|
||||
|
||||
app.namespace('/admin', function () {
|
||||
app.get('/', function (req, res) {
|
||||
Admin.buildHeader(req, res, function(err, header) {
|
||||
res.send(header + app.create_route('admin/index') + templates['admin/footer']);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/index', function (req, res) {
|
||||
Admin.buildHeader(req, res, function(err, header) {
|
||||
res.send(header + app.create_route('admin/index') + templates['admin/footer']);
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/category/uploadpicture', function(req, res) {
|
||||
if (!req.user) {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'],
|
||||
params = null, er;
|
||||
try {
|
||||
params = JSON.parse(req.body.params);
|
||||
} catch (e) {
|
||||
er = {
|
||||
error: 'Error uploading file! Error :' + e.message
|
||||
};
|
||||
return res.send(req.xhr ? er : JSON.stringify(er));
|
||||
}
|
||||
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
er = {
|
||||
error: 'Allowed image types are png, jpg and gif!'
|
||||
};
|
||||
res.send(req.xhr ? er : JSON.stringify(er));
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = 'category-' + params.cid + path.extname(req.files.userPhoto.name);
|
||||
|
||||
uploadImage(filename, req, res);
|
||||
});
|
||||
|
||||
app.post('/uploadfavicon', function(req, res) {
|
||||
if (!req.user) {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'],
|
||||
er;
|
||||
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
er = {error: 'You can only upload icon file type!'};
|
||||
res.send(req.xhr ? er : JSON.stringify(er));
|
||||
return;
|
||||
}
|
||||
|
||||
file.saveFileToLocal('favicon.ico', req.files.userPhoto.path, function(err, image) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
|
||||
if(err) {
|
||||
er = {error: err.message};
|
||||
return res.send(req.xhr ? er : JSON.stringify(er));
|
||||
}
|
||||
|
||||
var rs = {path: image.url};
|
||||
res.send(req.xhr ? rs : JSON.stringify(rs));
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/uploadlogo', function(req, res) {
|
||||
|
||||
if (!req.user) {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif'],
|
||||
er;
|
||||
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
er = {error: 'Allowed image types are png, jpg and gif!'};
|
||||
res.send(req.xhr ? er : JSON.stringify(er));
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = 'site-logo' + path.extname(req.files.userPhoto.name);
|
||||
|
||||
uploadImage(filename, req, res);
|
||||
});
|
||||
|
||||
app.get('/users/csv', function(req, res) {
|
||||
user.getUsersCSV(function(err, data) {
|
||||
res.attachment('users.csv');
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
res.end(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function uploadImage(filename, req, res) {
|
||||
function done(err, image) {
|
||||
var er, rs;
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
|
||||
if(err) {
|
||||
er = {error: err.message};
|
||||
return res.send(req.xhr ? er : JSON.stringify(er));
|
||||
}
|
||||
|
||||
rs = {path: image.url};
|
||||
res.send(req.xhr ? rs : JSON.stringify(rs));
|
||||
}
|
||||
|
||||
if(plugins.hasListeners('filter:uploadImage')) {
|
||||
plugins.fireHook('filter:uploadImage', req.files.userPhoto, done);
|
||||
} else {
|
||||
file.saveFileToLocal(filename, req.files.userPhoto.path, done);
|
||||
}
|
||||
if(err) {
|
||||
er = {error: err.message};
|
||||
return res.send(req.xhr ? er : JSON.stringify(er));
|
||||
}
|
||||
|
||||
var custom_routes = {
|
||||
'routes': [],
|
||||
'api': []
|
||||
rs = {path: image.url};
|
||||
res.send(req.xhr ? rs : JSON.stringify(rs));
|
||||
}
|
||||
|
||||
if(plugins.hasListeners('filter:uploadImage')) {
|
||||
plugins.fireHook('filter:uploadImage', req.files.userPhoto, done);
|
||||
} else {
|
||||
file.saveFileToLocal(filename, req.files.userPhoto.path, done);
|
||||
}
|
||||
}
|
||||
|
||||
function uploadCategoryPicture(req, res, next) {
|
||||
if (!req.user) {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'],
|
||||
params = null, er;
|
||||
try {
|
||||
params = JSON.parse(req.body.params);
|
||||
} catch (e) {
|
||||
er = {
|
||||
error: 'Error uploading file! Error :' + e.message
|
||||
};
|
||||
return res.send(req.xhr ? er : JSON.stringify(er));
|
||||
}
|
||||
|
||||
plugins.ready(function() {
|
||||
plugins.fireHook('filter:admin.create_routes', custom_routes, function(err, custom_routes) {
|
||||
var routes = custom_routes.routes;
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
er = {
|
||||
error: 'Allowed image types are png, jpg and gif!'
|
||||
};
|
||||
res.send(req.xhr ? er : JSON.stringify(er));
|
||||
return;
|
||||
}
|
||||
|
||||
for (var route in routes) {
|
||||
if (routes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app[routes[route].method || 'get']('/admin' + routes[route].route, function(req, res) {
|
||||
routes[route].options(req, res, function(options) {
|
||||
Admin.buildHeader(req, res, function (err, header) {
|
||||
res.send(header + options.content + templates['admin/footer']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
}
|
||||
var filename = 'category-' + params.cid + path.extname(req.files.userPhoto.name);
|
||||
|
||||
var apiRoutes = custom_routes.api;
|
||||
for (var route in apiRoutes) {
|
||||
if (apiRoutes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app[apiRoutes[route].method || 'get']('/api/admin' + apiRoutes[route].route, function(req, res) {
|
||||
apiRoutes[route].callback(req, res, function(data) {
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
uploadImage(filename, req, res);
|
||||
}
|
||||
|
||||
function uploadFavicon(req, res, next) {
|
||||
if (!req.user) {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'],
|
||||
er;
|
||||
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
er = {error: 'You can only upload icon file type!'};
|
||||
res.send(req.xhr ? er : JSON.stringify(er));
|
||||
return;
|
||||
}
|
||||
|
||||
file.saveFileToLocal('favicon.ico', req.files.userPhoto.path, function(err, image) {
|
||||
fs.unlink(req.files.userPhoto.path);
|
||||
|
||||
if(err) {
|
||||
er = {error: err.message};
|
||||
return res.send(req.xhr ? er : JSON.stringify(er));
|
||||
}
|
||||
|
||||
var rs = {path: image.url};
|
||||
res.send(req.xhr ? rs : JSON.stringify(rs));
|
||||
});
|
||||
}
|
||||
|
||||
function uploadLogo(req, res, next) {
|
||||
if (!req.user) {
|
||||
return res.redirect('/403');
|
||||
}
|
||||
|
||||
var allowedTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif'],
|
||||
er;
|
||||
|
||||
if (allowedTypes.indexOf(req.files.userPhoto.type) === -1) {
|
||||
er = {error: 'Allowed image types are png, jpg and gif!'};
|
||||
res.send(req.xhr ? er : JSON.stringify(er));
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = 'site-logo' + path.extname(req.files.userPhoto.name);
|
||||
|
||||
uploadImage(filename, req, res);
|
||||
}
|
||||
|
||||
function getUsersCSV(req, res, next) {
|
||||
user.getUsersCSV(function(err, data) {
|
||||
res.attachment('users.csv');
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
res.end(data);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(app, middleware, controllers) {
|
||||
app.all('/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI);
|
||||
app.all('/admin/*', middleware.admin.isAdmin);
|
||||
app.get('/admin', middleware.admin.isAdmin);
|
||||
|
||||
|
||||
app.namespace('/api/admin', function () {
|
||||
|
||||
app.get('/index', function (req, res) {
|
||||
app.get('/admin/', middleware.admin.buildHeader, controllers.admin.home);
|
||||
app.get('/admin/index', middleware.admin.buildHeader, controllers.admin.home);
|
||||
app.get('/api/admin/index', controllers.admin.home);
|
||||
|
||||
res.json({
|
||||
version: pkg.version,
|
||||
emailerInstalled: plugins.hasListeners('action:email.send'),
|
||||
searchInstalled: plugins.hasListeners('filter:search.query')
|
||||
});
|
||||
});
|
||||
app.get('/admin/users/search', middleware.admin.buildHeader, controllers.admin.users.search);
|
||||
app.get('/api/admin/users/search', controllers.admin.users.search);
|
||||
|
||||
app.get('/users/search', function (req, res) {
|
||||
res.json({
|
||||
search_display: 'block',
|
||||
loadmore_display: 'none',
|
||||
users: []
|
||||
});
|
||||
});
|
||||
app.get('/admin/users/latest', middleware.admin.buildHeader, controllers.admin.users.latest);
|
||||
app.get('/api/admin/users/latest', controllers.admin.users.latest);
|
||||
|
||||
app.get('/users/latest', function (req, res) {
|
||||
user.getUsers('users:joindate', 0, 49, function (err, data) {
|
||||
res.json({
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: data,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/users/sort-posts', middleware.admin.buildHeader, controllers.admin.users.sortByPosts);
|
||||
app.get('/api/admin/users/sort-posts', controllers.admin.users.sortByPosts);
|
||||
|
||||
app.get('/users/sort-posts', function (req, res) {
|
||||
user.getUsers('users:postcount', 0, 49, function (err, data) {
|
||||
res.json({
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: data,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/users/sort-reputation', middleware.admin.buildHeader, controllers.admin.users.sortByReputation);
|
||||
app.get('/api/admin/users/sort-reputation', controllers.admin.users.sortByReputation);
|
||||
|
||||
app.get('/users/sort-reputation', function (req, res) {
|
||||
user.getUsers('users:reputation', 0, 49, function (err, data) {
|
||||
res.json({
|
||||
search_display: 'none',
|
||||
loadmore_display: 'block',
|
||||
users: data,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/users', middleware.admin.buildHeader, controllers.admin.users.sortByJoinDate);
|
||||
app.get('/api/admin/users', controllers.admin.users.sortByJoinDate);
|
||||
|
||||
app.get('/users', function (req, res) {
|
||||
user.getUsers('users:joindate', 0, 49, function (err, data) {
|
||||
res.json({
|
||||
search_display: 'none',
|
||||
users: data,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/categories/active', middleware.admin.buildHeader, controllers.admin.categories.active);
|
||||
app.get('/api/admin/categories/active', controllers.admin.categories.active);
|
||||
|
||||
app.get('/categories', function (req, res) {
|
||||
categories.getAllCategories(0, function (err, data) {
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
app.get('/admin/categories/disabled', middleware.admin.buildHeader, controllers.admin.categories.disabled);
|
||||
app.get('/api/admin/categories/disabled', controllers.admin.categories.disabled);
|
||||
|
||||
app.get('/categories/active', function (req, res) {
|
||||
categories.getAllCategories(0, function (err, data) {
|
||||
data.categories = data.categories.filter(function (category) {
|
||||
return !category.disabled;
|
||||
});
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
app.get('/admin/topics', middleware.admin.buildHeader, controllers.admin.topics.get);
|
||||
app.get('/api/admin/topics', controllers.admin.topics.get);
|
||||
|
||||
app.get('/categories/disabled', function (req, res) {
|
||||
categories.getAllCategories(0, function (err, data) {
|
||||
data.categories = data.categories.filter(function (category) {
|
||||
return category.disabled;
|
||||
});
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
app.get('/admin/database', middleware.admin.buildHeader, controllers.admin.database.get);
|
||||
app.get('/api/admin/database', controllers.admin.database.get);
|
||||
|
||||
app.get('/topics', function (req, res) {
|
||||
topics.getAllTopics(0, 19, function (err, topics) {
|
||||
res.json({
|
||||
topics: topics,
|
||||
notopics: topics.length === 0
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/events', middleware.admin.buildHeader, controllers.admin.events.get);
|
||||
app.get('/api/admin/events', controllers.admin.events.get);
|
||||
|
||||
app.namespace('/database', function () {
|
||||
app.get('/', function (req, res) {
|
||||
db.info(function (err, data) {
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/plugins', middleware.admin.buildHeader, controllers.admin.plugins.get);
|
||||
app.get('/api/admin/plugins', controllers.admin.plugins.get);
|
||||
|
||||
app.get('/events', function(req, res, next) {
|
||||
events.getLog(function(err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
if(data) {
|
||||
data = data.toString().split('\n').reverse().join('\n');
|
||||
}
|
||||
res.json(200, {eventdata: data});
|
||||
});
|
||||
});
|
||||
app.get('/admin/languages', middleware.admin.buildHeader, controllers.admin.languages.get);
|
||||
app.get('/api/admin/languages', controllers.admin.languages.get);
|
||||
|
||||
app.get('/plugins', function (req, res) {
|
||||
plugins.showInstalled(function (err, plugins) {
|
||||
if (err || !Array.isArray(plugins)) plugins = [];
|
||||
app.get('/admin/settings', middleware.admin.buildHeader, controllers.admin.settings.get);
|
||||
app.get('/api/admin/settings', controllers.admin.settings.get);
|
||||
|
||||
res.json(200, {
|
||||
plugins: plugins
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/logger', middleware.admin.buildHeader, controllers.admin.logger.get);
|
||||
app.get('/api/admin/logger', controllers.admin.logger.get);
|
||||
|
||||
app.get('/languages', function(req, res) {
|
||||
Languages.list(function(err, languages) {
|
||||
res.send(200, {
|
||||
languages: languages
|
||||
});
|
||||
});
|
||||
});
|
||||
app.get('/admin/themes', middleware.admin.buildHeader, controllers.admin.themes.get);
|
||||
app.get('/api/admin/themes', controllers.admin.themes.get);
|
||||
|
||||
app.get('/settings', function (req, res) {
|
||||
res.json(200, {});
|
||||
});
|
||||
|
||||
app.get('/motd', function (req, res) {
|
||||
res.json(200, {});
|
||||
});
|
||||
|
||||
app.get('/logger', function(req, res) {
|
||||
res.json(200, {});
|
||||
});
|
||||
|
||||
app.get('/themes', function (req, res) {
|
||||
async.parallel({
|
||||
areas: function(next) {
|
||||
plugins.fireHook('filter:widgets.getAreas', [], next);
|
||||
},
|
||||
widgets: function(next) {
|
||||
plugins.fireHook('filter:widgets.getWidgets', [], next);
|
||||
}
|
||||
}, function(err, data) {
|
||||
async.each(data.areas, function(area, next) {
|
||||
widgets.getArea(area.template, area.location, function(err, areaData) {
|
||||
area.data = areaData;
|
||||
next(err);
|
||||
});
|
||||
}, function(err) {
|
||||
for (var w in data.widgets) {
|
||||
if (data.widgets.hasOwnProperty(w)) {
|
||||
data.widgets[w].content += "<br /><label>Title:</label><input type=\"text\" class=\"form-control\" name=\"title\" placeholder=\"Title (only shown on some containers)\" /><br /><label>Container:</label><textarea rows=\"4\" class=\"form-control container-html\" name=\"container\" placeholder=\"Drag and drop a container or enter HTML here.\"></textarea>";
|
||||
}
|
||||
}
|
||||
|
||||
res.json(200, {
|
||||
areas: data.areas,
|
||||
widgets: data.widgets
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/testing/categories', function (req, res) {
|
||||
res.json(200, {});
|
||||
});
|
||||
|
||||
app.get('/groups', function (req, res) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
groups.list({
|
||||
expand: true
|
||||
}, next);
|
||||
},
|
||||
function(next) {
|
||||
groups.listSystemGroups({
|
||||
expand: true
|
||||
}, next);
|
||||
}
|
||||
], function(err, data) {
|
||||
var groups = data[0].concat(data[1]);
|
||||
|
||||
res.json(200, {
|
||||
groups: groups,
|
||||
yourid: req.user.uid
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
app.get('/admin/groups', middleware.admin.buildHeader, controllers.admin.groups.get);
|
||||
app.get('/api/admin/groups', controllers.admin.groups.get);
|
||||
|
||||
|
||||
}(exports));
|
||||
app.namespace('/admin', function () {
|
||||
app.get('/users/csv', getUsersCSV);
|
||||
|
||||
app.post('/category/uploadpicture', uploadCategoryPicture);
|
||||
app.post('/uploadfavicon', uploadFavicon);
|
||||
app.post('/uploadlogo', uploadLogo);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,551 +1,121 @@
|
||||
"use strict";
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('nconf'),
|
||||
async = require('async'),
|
||||
fs = require('fs'),
|
||||
nconf = require('nconf'),
|
||||
|
||||
db = require('../database'),
|
||||
user = require('../user'),
|
||||
groups = require('../groups'),
|
||||
auth = require('./authentication'),
|
||||
topics = require('../topics'),
|
||||
ThreadTools = require('../threadTools'),
|
||||
posts = require('../posts'),
|
||||
categories = require('../categories'),
|
||||
categoryTools = require('../categoryTools'),
|
||||
meta = require('../meta'),
|
||||
Plugins = require('../plugins'),
|
||||
utils = require('../../public/src/utils'),
|
||||
translator = require('../../public/src/translator'),
|
||||
pkg = require('../../package.json');
|
||||
user = require('./../user'),
|
||||
topics = require('./../topics'),
|
||||
posts = require('./../posts'),
|
||||
categories = require('./../categories'),
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins'),
|
||||
utils = require('./../../public/src/utils'),
|
||||
pkg = require('./../../package.json');
|
||||
|
||||
|
||||
(function (Api) {
|
||||
Api.createRoutes = function (app) {
|
||||
|
||||
app.namespace('/api', function () {
|
||||
|
||||
app.all('*', function(req, res, next) {
|
||||
|
||||
if(req.user) {
|
||||
user.updateLastOnlineTime(req.user.uid);
|
||||
}
|
||||
|
||||
db.sortedSetAdd('ip:recent', Date.now(), req.ip || 'Unknown');
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/get_templates_listing', function (req, res) {
|
||||
utils.walk(path.join(__dirname, '../../', 'public/templates'), function (err, data) {
|
||||
res.json(data.concat(app.get_custom_templates()).filter(function(value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/config', function (req, res, next) {
|
||||
var config = require('../../public/config.json');
|
||||
|
||||
config.version = pkg.version;
|
||||
config.postDelay = meta.config.postDelay;
|
||||
config.minimumTitleLength = meta.config.minimumTitleLength;
|
||||
config.maximumTitleLength = meta.config.maximumTitleLength;
|
||||
config.minimumPostLength = meta.config.minimumPostLength;
|
||||
config.hasImageUploadPlugin = Plugins.hasListeners('filter:uploadImage');
|
||||
config.maximumProfileImageSize = meta.config.maximumProfileImageSize;
|
||||
config.minimumUsernameLength = meta.config.minimumUsernameLength;
|
||||
config.maximumUsernameLength = meta.config.maximumUsernameLength;
|
||||
config.minimumPasswordLength = meta.config.minimumPasswordLength;
|
||||
config.maximumSignatureLength = meta.config.maximumSignatureLength;
|
||||
config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1;
|
||||
config.allowGuestPosting = parseInt(meta.config.allowGuestPosting, 10) === 1;
|
||||
config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1;
|
||||
config.allowTopicsThumbnail = parseInt(meta.config.allowTopicsThumbnail, 10) === 1;
|
||||
config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
|
||||
config.disableSocialButtons = parseInt(meta.config.disableSocialButtons, 10) === 1;
|
||||
config.topicsPerPage = meta.config.topicsPerPage || 20;
|
||||
config.postsPerPage = meta.config.postsPerPage || 20;
|
||||
config.maximumFileSize = meta.config.maximumFileSize;
|
||||
config['theme:id'] = meta.config['theme:id'];
|
||||
config.defaultLang = meta.config.defaultLang || 'en_GB';
|
||||
config.environment = process.env.NODE_ENV;
|
||||
|
||||
if (!req.user) {
|
||||
return res.json(200, config);
|
||||
}
|
||||
|
||||
if(req.user) {
|
||||
user.getSettings(req.user.uid, function(err, settings) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
config.usePagination = settings.usePagination;
|
||||
config.topicsPerPage = settings.topicsPerPage;
|
||||
config.postsPerPage = settings.postsPerPage;
|
||||
res.json(200, config);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/home', function (req, res) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
categories.getAllCategories(uid, function (err, data) {
|
||||
|
||||
data.categories = data.categories.filter(function (category) {
|
||||
return !category.disabled;
|
||||
});
|
||||
|
||||
function canSee(category, next) {
|
||||
categoryTools.privileges(category.cid, ((req.user) ? req.user.uid || 0 : 0), function(err, privileges) {
|
||||
next(!err && privileges.read);
|
||||
});
|
||||
}
|
||||
|
||||
function getRecentReplies(category, callback) {
|
||||
categories.getRecentReplies(category.cid, uid, parseInt(category.numRecentReplies, 10), function (err, posts) {
|
||||
category.posts = posts;
|
||||
category.post_count = posts.length > 2 ? 2 : posts.length; // this was a hack to make metro work back in the day, post_count should just = length
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
async.filter(data.categories, canSee, function(visibleCategories) {
|
||||
data.categories = visibleCategories;
|
||||
|
||||
async.each(data.categories, getRecentReplies, function (err) {
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/login', function (req, res) {
|
||||
var data = {},
|
||||
login_strategies = auth.get_login_strategies(),
|
||||
num_strategies = login_strategies.length,
|
||||
emailersPresent = Plugins.hasListeners('action:email.send');
|
||||
|
||||
if (num_strategies == 0) {
|
||||
data = {
|
||||
'login_window:spansize': 'col-md-12',
|
||||
'alternate_logins': false
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
'login_window:spansize': 'col-md-6',
|
||||
'alternate_logins': true
|
||||
}
|
||||
}
|
||||
|
||||
data.authentication = login_strategies;
|
||||
data.token = res.locals.csrf_token;
|
||||
data.showResetLink = emailersPresent;
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
app.get('/register', function (req, res) {
|
||||
var data = {},
|
||||
login_strategies = auth.get_login_strategies(),
|
||||
num_strategies = login_strategies.length;
|
||||
|
||||
if (num_strategies == 0) {
|
||||
data = {
|
||||
'register_window:spansize': 'col-md-12',
|
||||
'alternate_logins': false
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
'register_window:spansize': 'col-md-6',
|
||||
'alternate_logins': true
|
||||
}
|
||||
}
|
||||
|
||||
data.authentication = login_strategies;
|
||||
|
||||
data.token = res.locals.csrf_token;
|
||||
data.minimumUsernameLength = meta.config.minimumUsernameLength;
|
||||
data.maximumUsernameLength = meta.config.maximumUsernameLength;
|
||||
data.minimumPasswordLength = meta.config.minimumPasswordLength;
|
||||
data.termsOfUse = meta.config.termsOfUse;
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
app.get('/topic/:id/:slug?', function (req, res, next) {
|
||||
var uid = req.user? parseInt(req.user.uid, 10) : 0;
|
||||
var tid = req.params.id;
|
||||
var page = 1;
|
||||
if(req.query && req.query.page) {
|
||||
page = req.query.page;
|
||||
}
|
||||
|
||||
if(!utils.isNumber(page) || parseInt(page, 10) < 1) {
|
||||
return res.send(404);
|
||||
}
|
||||
|
||||
user.getSettings(uid, function(err, settings) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var start = (page - 1) * settings.postsPerPage;
|
||||
var end = start + settings.postsPerPage - 1;
|
||||
|
||||
ThreadTools.privileges(tid, uid, function(err, privileges) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!privileges.read) {
|
||||
res.send(403);
|
||||
}
|
||||
|
||||
topics.getTopicWithPosts(tid, uid, start, end, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(page > data.pageCount) {
|
||||
return res.send(404);
|
||||
}
|
||||
|
||||
if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) {
|
||||
return res.json(404, {});
|
||||
}
|
||||
|
||||
data.currentPage = page;
|
||||
data.privileges = privileges;
|
||||
|
||||
if (uid) {
|
||||
topics.markAsRead(tid, uid, function(err) {
|
||||
topics.pushUnreadCount(uid);
|
||||
});
|
||||
}
|
||||
|
||||
topics.increaseViewCount(tid);
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/category/:id/:slug?', function (req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
var page = 1;
|
||||
if(req.query && req.query.page) {
|
||||
page = req.query.page;
|
||||
}
|
||||
|
||||
if(!utils.isNumber(page) || parseInt(page, 10) < 1) {
|
||||
return res.send(404);
|
||||
}
|
||||
|
||||
user.getSettings(uid, function(err, settings) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var start = (page - 1) * settings.topicsPerPage,
|
||||
end = start + settings.topicsPerPage - 1;
|
||||
|
||||
categoryTools.privileges(req.params.id, uid, function(err, privileges) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!privileges.read) {
|
||||
return res.send(403);
|
||||
}
|
||||
|
||||
categories.getCategoryById(req.params.id, start, end, uid, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data.currentPage = page;
|
||||
data.privileges = privileges;
|
||||
|
||||
if (data && !data.disabled) {
|
||||
res.json(data);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/recent/posts/:term?', function (req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
posts.getRecentPosts(uid, 0, 19, req.params.term, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/recent/:term?', function (req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/popular/:set?', function (req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
var set = 'topics:' + req.params.set;
|
||||
if(!req.params.set) {
|
||||
set = 'topics:posts';
|
||||
}
|
||||
topics.getTopicsFromSet(uid, set, 0, 19, function(err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/unread', function (req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
if(!req.user) {
|
||||
return res.json(403, 'not-allowed');
|
||||
}
|
||||
topics.getUnreadTopics(uid, 0, 19, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/unread/total', function (req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
if(!req.user) {
|
||||
return res.json(403, 'not-allowed');
|
||||
}
|
||||
topics.getTotalUnread(uid, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/notifications', function(req, res) {
|
||||
if (req.user && req.user.uid) {
|
||||
user.notifications.getAll(req.user.uid, null, null, function(err, notifications) {
|
||||
res.json({
|
||||
notifications: notifications
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/confirm/:id', function (req, res) {
|
||||
user.email.confirm(req.params.id, function (data) {
|
||||
if (data.status === 'ok') {
|
||||
res.json({
|
||||
'alert-class': 'alert-success',
|
||||
title: 'Email Confirmed',
|
||||
text: 'Thank you for vaidating your email. Your account is now fully activated.'
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
'alert-class': 'alert-danger',
|
||||
title: 'An error occurred...',
|
||||
text: 'There was a problem validating your email address. Perhaps the code was invalid or has expired.'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/outgoing', function (req, res) {
|
||||
var url = req.query.url;
|
||||
|
||||
if (url) {
|
||||
res.json({
|
||||
url: url,
|
||||
title: meta.config.title
|
||||
});
|
||||
} else {
|
||||
res.status(404);
|
||||
res.redirect(nconf.get('relative_path') + '/404');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/search', function (req, res) {
|
||||
if ((req.user && req.user.uid) || meta.config.allowGuestSearching === '1') {
|
||||
return res.json({
|
||||
show_no_topics: 'hide',
|
||||
show_no_posts: 'hide',
|
||||
show_results: 'hide',
|
||||
search_query: '',
|
||||
posts: [],
|
||||
topics: []
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/search/:term', function (req, res, next) {
|
||||
if (!Plugins.hasListeners('filter:search.query')) {
|
||||
return res.redirect('/404');
|
||||
}
|
||||
|
||||
function searchPosts(callback) {
|
||||
Plugins.fireHook('filter:search.query', {
|
||||
index: 'post',
|
||||
query: req.params.term
|
||||
}, function(err, pids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
posts.getPostSummaryByPids(pids, false, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function searchTopics(callback) {
|
||||
Plugins.fireHook('filter:search.query', {
|
||||
index: 'topic',
|
||||
query: req.params.term
|
||||
}, function(err, tids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
topics.getTopicsByTids(tids, 0, callback);
|
||||
});
|
||||
}
|
||||
|
||||
if ((req.user && req.user.uid) || meta.config.allowGuestSearching === '1') {
|
||||
async.parallel([searchPosts, searchTopics], function (err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!results) {
|
||||
results = [];
|
||||
results[0] = results[1] = [];
|
||||
}
|
||||
|
||||
return res.json({
|
||||
show_no_topics: results[1].length ? 'hide' : '',
|
||||
show_no_posts: results[0].length ? 'hide' : '',
|
||||
show_results: '',
|
||||
search_query: req.params.term,
|
||||
posts: results[0],
|
||||
topics: results[1],
|
||||
post_matches : results[0].length,
|
||||
topic_matches : results[1].length
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
|
||||
function upload(req, res, filesIterator, next) {
|
||||
if(!req.user) {
|
||||
return res.json(403, {message:'not allowed'});
|
||||
}
|
||||
var files = req.files.files;
|
||||
|
||||
if(!Array.isArray(files)) {
|
||||
return res.json(500, {message: 'invalid files'});
|
||||
}
|
||||
|
||||
// multiple files
|
||||
if(Array.isArray(files[0])) {
|
||||
files = files[0];
|
||||
}
|
||||
|
||||
function deleteTempFiles() {
|
||||
for(var i=0; i<files.length; ++i) {
|
||||
fs.unlink(files[i].path);
|
||||
}
|
||||
}
|
||||
|
||||
async.map(files, filesIterator, function(err, images) {
|
||||
deleteTempFiles();
|
||||
|
||||
if(err) {
|
||||
return res.send(500, err.message);
|
||||
}
|
||||
|
||||
// if this was not a XMLHttpRequest (hence the req.xhr check http://expressjs.com/api.html#req.xhr)
|
||||
// then most likely it's submit via the iFrame workaround, via the jquery.form plugin's ajaxSubmit()
|
||||
// we need to send it as text/html so IE8 won't trigger a file download for the json response
|
||||
// malsup.com/jquery/form/#file-upload
|
||||
|
||||
// Also, req.send is safe for both types, if the response was an object, res.send will automatically submit as application/json
|
||||
// expressjs.com/api.html#res.send
|
||||
res.send(200, req.xhr ? images : JSON.stringify(images));
|
||||
});
|
||||
}
|
||||
|
||||
app.post('/post/upload', function(req, res, next) {
|
||||
upload(req, res, function(file, next) {
|
||||
if(file.type.match(/image./)) {
|
||||
posts.uploadPostImage(file, next);
|
||||
} else {
|
||||
posts.uploadPostFile(file, next);
|
||||
}
|
||||
}, next)
|
||||
});
|
||||
|
||||
app.post('/topic/thumb/upload', function(req, res, next) {
|
||||
upload(req, res, function(file, next) {
|
||||
if(file.type.match(/image./)) {
|
||||
topics.uploadTopicThumb(file, next);
|
||||
} else {
|
||||
res.json(500, {message: 'Invalid File'});
|
||||
}
|
||||
}, next);
|
||||
});
|
||||
|
||||
app.get('/reset', function (req, res) {
|
||||
res.json({});
|
||||
});
|
||||
|
||||
app.get('/reset/:code', function (req, res) {
|
||||
res.json({
|
||||
reset_code: req.params.code
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/404', function (req, res) {
|
||||
res.json({});
|
||||
});
|
||||
|
||||
app.get('/403', function (req, res) {
|
||||
res.json({});
|
||||
});
|
||||
|
||||
app.get('/500', function(req, res) {
|
||||
res.json({errorMessage: 'testing'});
|
||||
});
|
||||
|
||||
app.namespace('/categories', function() {
|
||||
app.get(':cid/moderators', function(req, res) {
|
||||
categories.getModerators(req.params.cid, function(err, moderators) {
|
||||
res.json({moderators: moderators});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
function upload(req, res, filesIterator, next) {
|
||||
if(!req.user) {
|
||||
return res.json(403, {message:'not allowed'});
|
||||
}
|
||||
}(exports));
|
||||
var files = req.files.files;
|
||||
|
||||
if(!Array.isArray(files)) {
|
||||
return res.json(500, {message: 'invalid files'});
|
||||
}
|
||||
|
||||
if(Array.isArray(files[0])) {
|
||||
files = files[0];
|
||||
}
|
||||
|
||||
function deleteTempFiles() {
|
||||
for(var i=0; i<files.length; ++i) {
|
||||
fs.unlink(files[i].path);
|
||||
}
|
||||
}
|
||||
|
||||
async.map(files, filesIterator, function(err, images) {
|
||||
deleteTempFiles();
|
||||
|
||||
if(err) {
|
||||
return res.send(500, err.message);
|
||||
}
|
||||
|
||||
// IE8 - send it as text/html so browser won't trigger a file download for the json response
|
||||
// malsup.com/jquery/form/#file-upload
|
||||
res.send(200, req.xhr ? images : JSON.stringify(images));
|
||||
});
|
||||
}
|
||||
|
||||
function uploadPost(req, res, next) {
|
||||
upload(req, res, function(file, next) {
|
||||
if(file.type.match(/image./)) {
|
||||
posts.uploadPostImage(file, next);
|
||||
} else {
|
||||
posts.uploadPostFile(file, next);
|
||||
}
|
||||
}, next);
|
||||
}
|
||||
|
||||
function uploadThumb(req, res, next) {
|
||||
upload(req, res, function(file, next) {
|
||||
if(file.type.match(/image./)) {
|
||||
topics.uploadTopicThumb(file, next);
|
||||
} else {
|
||||
res.json(500, {message: 'Invalid File'});
|
||||
}
|
||||
}, next);
|
||||
}
|
||||
|
||||
function getModerators(req, res, next) {
|
||||
categories.getModerators(req.params.cid, function(err, moderators) {
|
||||
res.json({moderators: moderators});
|
||||
});
|
||||
}
|
||||
|
||||
function getTemplatesListing(req, res, next) {
|
||||
utils.walk(nconf.get('views_dir'), function (err, data) {
|
||||
data = data
|
||||
.filter(function(value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
}).map(function(el) {
|
||||
return el.replace(nconf.get('views_dir') + '/', '');
|
||||
});
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
}
|
||||
|
||||
function getRecentPosts(req, res, next) {
|
||||
var uid = (req.user) ? req.user.uid : 0;
|
||||
|
||||
posts.getRecentPosts(uid, 0, 19, req.params.term, function (err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(app, middleware, controllers) {
|
||||
app.namespace('/api', function () {
|
||||
app.all('*', middleware.updateLastOnlineTime, middleware.prepareAPI);
|
||||
|
||||
app.get('/config', controllers.api.getConfig);
|
||||
|
||||
app.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.accounts.getUserByUID);
|
||||
app.get('/get_templates_listing', getTemplatesListing);
|
||||
app.get('/categories/:cid/moderators', getModerators);
|
||||
app.get('/recent/posts/:term?', getRecentPosts);
|
||||
|
||||
app.post('/post/upload', uploadPost);
|
||||
app.post('/topic/thumb/upload', uploadThumb);
|
||||
});
|
||||
|
||||
// this should be in the API namespace
|
||||
// also, perhaps pass in :userslug so we can use checkAccountPermissions middleware - in future will allow admins to upload a picture for a user
|
||||
app.post('/user/uploadpicture', middleware.authenticate, middleware.checkGlobalPrivacySettings, /*middleware.checkAccountPermissions,*/ controllers.accounts.uploadPicture);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
(function(Auth) {
|
||||
"use strict";
|
||||
|
||||
var passport = require('passport'),
|
||||
passportLocal = require('passport-local').Strategy,
|
||||
nconf = require('nconf'),
|
||||
bcrypt = require('bcryptjs'),
|
||||
winston = require('winston'),
|
||||
|
||||
meta = require('../meta'),
|
||||
user = require('../user'),
|
||||
plugins = require('../plugins'),
|
||||
utils = require('../../public/src/utils'),
|
||||
meta = require('./../meta'),
|
||||
user = require('./../user'),
|
||||
plugins = require('./../plugins'),
|
||||
utils = require('./../../public/src/utils'),
|
||||
templates = require('./../../public/src/templates'),
|
||||
|
||||
login_strategies = [];
|
||||
@@ -26,16 +28,16 @@
|
||||
Auth.initialize = function(app) {
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Auth.get_login_strategies = function() {
|
||||
return login_strategies;
|
||||
}
|
||||
};
|
||||
|
||||
Auth.registerApp = function(app) {
|
||||
Auth.app = app;
|
||||
}
|
||||
};
|
||||
|
||||
Auth.createRoutes = function(app) {
|
||||
app.namespace(nconf.get('relative_path'), function () {
|
||||
@@ -49,39 +51,23 @@
|
||||
req.logout();
|
||||
}
|
||||
|
||||
res.send(200)
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
for (var i in login_strategies) {
|
||||
var strategy = login_strategies[i];
|
||||
app.get(strategy.url, passport.authenticate(strategy.name, {
|
||||
scope: strategy.scope
|
||||
}));
|
||||
if (login_strategies.hasOwnProperty(i)) {
|
||||
var strategy = login_strategies[i];
|
||||
app.get(strategy.url, passport.authenticate(strategy.name, {
|
||||
scope: strategy.scope
|
||||
}));
|
||||
|
||||
app.get(strategy.callbackURL, passport.authenticate(strategy.name, {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
}));
|
||||
app.get(strategy.callbackURL, passport.authenticate(strategy.name, {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/reset/:code', function(req, res) {
|
||||
app.build_header({
|
||||
req: req,
|
||||
res: res
|
||||
}, function(err, header) {
|
||||
res.send(header + app.create_route('reset/' + req.params.code) + templates['footer']);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/reset', function(req, res) {
|
||||
app.build_header({
|
||||
req: req,
|
||||
res: res
|
||||
}, function(err, header) {
|
||||
res.send(header + app.create_route('reset') + templates['footer']);
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/login', function(req, res, next) {
|
||||
passport.authenticate('local', function(err, userData, info) {
|
||||
if (err) {
|
||||
@@ -127,10 +113,11 @@
|
||||
|
||||
require('../socket.io').emitUserCount();
|
||||
|
||||
if(req.body.referrer)
|
||||
if(req.body.referrer) {
|
||||
res.redirect(req.body.referrer);
|
||||
else
|
||||
} else {
|
||||
res.redirect(nconf.get('relative_path') + '/');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.redirect(nconf.get('relative_path') + '/register');
|
||||
@@ -138,7 +125,7 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Auth.login = function(username, password, next) {
|
||||
if (!username || !password) {
|
||||
@@ -186,7 +173,7 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
passport.use(new passportLocal(Auth.login));
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
var user = require('./../user'),
|
||||
categories = require('./../categories'),
|
||||
topics = require('./../topics'),
|
||||
posts = require('./../posts');
|
||||
|
||||
var DebugRoute = function(app) {
|
||||
|
||||
module.exports = function(app, middleware, controllers) {
|
||||
app.namespace('/debug', function() {
|
||||
|
||||
app.get('/uid/:uid', function (req, res) {
|
||||
|
||||
if (!req.params.uid)
|
||||
if (!req.params.uid) {
|
||||
return res.redirect('/404');
|
||||
}
|
||||
|
||||
user.getUserData(req.params.uid, function (err, data) {
|
||||
if (data) {
|
||||
@@ -53,12 +52,5 @@ var DebugRoute = function(app) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/test', function(req, res) {
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = DebugRoute;
|
||||
};
|
||||
@@ -1,201 +1,202 @@
|
||||
(function (Feeds) {
|
||||
var posts = require('../posts'),
|
||||
topics = require('../topics'),
|
||||
categories = require('../categories'),
|
||||
"use strict";
|
||||
|
||||
rss = require('rss'),
|
||||
nconf = require('nconf'),
|
||||
var posts = require('./../posts'),
|
||||
topics = require('./../topics'),
|
||||
categories = require('./../categories'),
|
||||
|
||||
ThreadTools = require('../threadTools'),
|
||||
CategoryTools = require('../categoryTools');
|
||||
rss = require('rss'),
|
||||
nconf = require('nconf'),
|
||||
|
||||
Feeds.createRoutes = function(app){
|
||||
app.get('/topic/:topic_id.rss', hasTopicPrivileges, generateForTopic);
|
||||
app.get('/category/:category_id.rss', hasCategoryPrivileges, generateForCategory);
|
||||
app.get('/recent.rss', generateForRecent);
|
||||
app.get('/popular.rss', generateForPopular);
|
||||
};
|
||||
ThreadTools = require('./../threadTools'),
|
||||
CategoryTools = require('./../categoryTools');
|
||||
|
||||
function hasTopicPrivileges(req, res, next) {
|
||||
var tid = req.params.topic_id;
|
||||
var uid = req.user ? req.user.uid || 0 : 0;
|
||||
|
||||
ThreadTools.privileges(tid, uid, function(err, privileges) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!privileges.read) {
|
||||
return res.redirect('403');
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
function hasCategoryPrivileges(req, res, next) {
|
||||
var cid = req.params.category_id;
|
||||
var uid = req.user ? req.user.uid || 0 : 0;
|
||||
|
||||
CategoryTools.privileges(cid, uid, function(err, privileges) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!privileges.read) {
|
||||
return res.redirect('403');
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function hasTopicPrivileges(req, res, next) {
|
||||
var tid = req.params.topic_id;
|
||||
var uid = req.user ? req.user.uid || 0 : 0;
|
||||
function generateForTopic(req, res, next) {
|
||||
var tid = req.params.topic_id;
|
||||
|
||||
ThreadTools.privileges(tid, uid, function(err, privileges) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
topics.getTopicWithPosts(tid, 0, 0, 25, function (err, topicData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if(!privileges.read) {
|
||||
return res.redirect('403');
|
||||
}
|
||||
var description = topicData.posts.length ? topicData.posts[0].content : '';
|
||||
var image_url = topicData.posts.length ? topicData.posts[0].picture : '';
|
||||
var author = topicData.posts.length ? topicData.posts[0].username : '';
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
var feed = new rss({
|
||||
title: topicData.title,
|
||||
description: description,
|
||||
feed_url: nconf.get('url') + '/topic/' + tid + '.rss',
|
||||
site_url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
image_url: image_url,
|
||||
author: author,
|
||||
ttl: 60
|
||||
}),
|
||||
dateStamp;
|
||||
|
||||
function hasCategoryPrivileges(req, res, next) {
|
||||
var cid = req.params.category_id;
|
||||
var uid = req.user ? req.user.uid || 0 : 0;
|
||||
// Add pubDate if topic contains posts
|
||||
if (topicData.posts.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString();
|
||||
}
|
||||
|
||||
CategoryTools.privileges(cid, uid, function(err, privileges) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
topicData.posts.forEach(function(postData) {
|
||||
if (parseInt(postData.deleted, 10) === 0) {
|
||||
dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString();
|
||||
|
||||
if(!privileges.read) {
|
||||
return res.redirect('403');
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function generateForTopic(req, res, next) {
|
||||
var tid = req.params.topic_id;
|
||||
|
||||
topics.getTopicWithPosts(tid, 0, 0, 25, function (err, topicData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var description = topicData.posts.length ? topicData.posts[0].content : '';
|
||||
var image_url = topicData.posts.length ? topicData.posts[0].picture : '';
|
||||
var author = topicData.posts.length ? topicData.posts[0].username : '';
|
||||
|
||||
var feed = new rss({
|
||||
title: topicData.title,
|
||||
description: description,
|
||||
feed_url: nconf.get('url') + '/topic/' + tid + '.rss',
|
||||
site_url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
image_url: image_url,
|
||||
author: author,
|
||||
ttl: 60
|
||||
}),
|
||||
dateStamp;
|
||||
|
||||
// Add pubDate if topic contains posts
|
||||
if (topicData.posts.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString();
|
||||
}
|
||||
|
||||
topicData.posts.forEach(function(postData) {
|
||||
if (parseInt(postData.deleted, 10) === 0) {
|
||||
dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString();
|
||||
|
||||
feed.item({
|
||||
title: 'Reply to ' + topicData.title + ' on ' + dateStamp,
|
||||
description: postData.content,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug + '#' + postData.pid,
|
||||
author: postData.username,
|
||||
date: dateStamp
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
function generateForCategory(req, res, next) {
|
||||
var cid = req.params.category_id;
|
||||
|
||||
categories.getCategoryById(cid, 0, 25, 0, function (err, categoryData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var feed = new rss({
|
||||
title: categoryData.name,
|
||||
description: categoryData.description,
|
||||
feed_url: nconf.get('url') + '/category/' + cid + '.rss',
|
||||
site_url: nconf.get('url') + '/category/' + categoryData.cid,
|
||||
ttl: 60
|
||||
});
|
||||
|
||||
// Add pubDate if category has topics
|
||||
if (categoryData.topics.length > 0) feed.pubDate = new Date(parseInt(categoryData.topics[0].lastposttime, 10)).toUTCString();
|
||||
|
||||
categoryData.topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
title: 'Reply to ' + topicData.title + ' on ' + dateStamp,
|
||||
description: postData.content,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug + '#' + postData.pid,
|
||||
author: postData.username,
|
||||
date: dateStamp
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function generateForCategory(req, res, next) {
|
||||
var cid = req.params.category_id;
|
||||
|
||||
categories.getCategoryById(cid, 0, 25, 0, function (err, categoryData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var feed = new rss({
|
||||
title: categoryData.name,
|
||||
description: categoryData.description,
|
||||
feed_url: nconf.get('url') + '/category/' + cid + '.rss',
|
||||
site_url: nconf.get('url') + '/category/' + categoryData.cid,
|
||||
ttl: 60
|
||||
});
|
||||
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
// Add pubDate if category has topics
|
||||
if (categoryData.topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(categoryData.topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
categoryData.topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function generateForRecent(req, res, next) {
|
||||
topics.getLatestTopics(0, 0, 19, 'month', function (err, recentData) {
|
||||
if(err){
|
||||
return next(err);
|
||||
}
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
});
|
||||
}
|
||||
|
||||
var feed = new rss({
|
||||
title: 'Recently Active Topics',
|
||||
description: 'A list of topics that have been active within the past 24 hours',
|
||||
feed_url: nconf.get('url') + '/recent.rss',
|
||||
site_url: nconf.get('url') + '/recent',
|
||||
ttl: 60
|
||||
});
|
||||
function generateForRecent(req, res, next) {
|
||||
topics.getLatestTopics(0, 0, 19, 'month', function (err, recentData) {
|
||||
if(err){
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Add pubDate if recent topics list contains topics
|
||||
if (recentData.topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(recentData.topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
recentData.topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
var feed = new rss({
|
||||
title: 'Recently Active Topics',
|
||||
description: 'A list of topics that have been active within the past 24 hours',
|
||||
feed_url: nconf.get('url') + '/recent.rss',
|
||||
site_url: nconf.get('url') + '/recent',
|
||||
ttl: 60
|
||||
});
|
||||
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
// Add pubDate if recent topics list contains topics
|
||||
if (recentData.topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(recentData.topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
recentData.topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function generateForPopular(req, res, next) {
|
||||
topics.getTopicsFromSet(0, 'topics:posts', 0, 19, function (err, popularData) {
|
||||
if(err){
|
||||
return next(err);
|
||||
}
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
});
|
||||
}
|
||||
|
||||
var feed = new rss({
|
||||
title: 'Popular Topics',
|
||||
description: 'A list of topics that are sorted by post count',
|
||||
feed_url: nconf.get('url') + '/popular.rss',
|
||||
site_url: nconf.get('url') + '/popular',
|
||||
ttl: 60
|
||||
});
|
||||
function generateForPopular(req, res, next) {
|
||||
topics.getTopicsFromSet(0, 'topics:posts', 0, 19, function (err, popularData) {
|
||||
if(err){
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Add pubDate if recent topics list contains topics
|
||||
if (popularData.topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(popularData.topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
popularData.topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
var feed = new rss({
|
||||
title: 'Popular Topics',
|
||||
description: 'A list of topics that are sorted by post count',
|
||||
feed_url: nconf.get('url') + '/popular.rss',
|
||||
site_url: nconf.get('url') + '/popular',
|
||||
ttl: 60
|
||||
});
|
||||
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
// Add pubDate if recent topics list contains topics
|
||||
if (popularData.topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(popularData.topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
popularData.topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
});
|
||||
};
|
||||
}(exports));
|
||||
|
||||
var xml = feed.xml();
|
||||
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(app, middleware, controllers){
|
||||
app.get('/topic/:topic_id.rss', hasTopicPrivileges, generateForTopic);
|
||||
app.get('/category/:category_id.rss', hasCategoryPrivileges, generateForCategory);
|
||||
app.get('/recent.rss', generateForRecent);
|
||||
app.get('/popular.rss', generateForPopular);
|
||||
};
|
||||
131
src/routes/index.js
Normal file
131
src/routes/index.js
Normal file
@@ -0,0 +1,131 @@
|
||||
"use strict";
|
||||
|
||||
var nconf = require('nconf'),
|
||||
controllers = require('./../controllers'),
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins'),
|
||||
|
||||
metaRoutes = require('./meta'),
|
||||
apiRoutes = require('./api'),
|
||||
adminRoutes = require('./admin'),
|
||||
feedRoutes = require('./feeds'),
|
||||
pluginRoutes = require('./plugins');
|
||||
|
||||
|
||||
module.exports = function(app, middleware) {
|
||||
app.namespace(nconf.get('relative_path'), function() {
|
||||
adminRoutes(app, middleware, controllers);
|
||||
metaRoutes(app, middleware, controllers);
|
||||
apiRoutes(app, middleware, controllers);
|
||||
feedRoutes(app, middleware, controllers);
|
||||
|
||||
/**
|
||||
* Every view has an associated API route.
|
||||
*
|
||||
*/
|
||||
/* Main */
|
||||
app.get('/', middleware.buildHeader, controllers.home);
|
||||
app.get('/api/home', controllers.home);
|
||||
|
||||
app.get('/login', middleware.buildHeader, controllers.login);
|
||||
app.get('/api/login', controllers.login);
|
||||
|
||||
app.get('/register', middleware.buildHeader, controllers.register);
|
||||
app.get('/api/register', controllers.register);
|
||||
|
||||
app.get('/confirm/:code', middleware.buildHeader, controllers.confirmEmail);
|
||||
app.get('/api/confirm/:code', controllers.confirmEmail);
|
||||
|
||||
app.get('/outgoing', middleware.buildHeader, controllers.outgoing);
|
||||
app.get('/api/outgoing', controllers.outgoing);
|
||||
|
||||
app.get('/search/:term?', middleware.buildHeader, middleware.guestSearchingAllowed, controllers.search);
|
||||
app.get('/api/search/:term', middleware.guestSearchingAllowed, controllers.search);
|
||||
|
||||
app.get('/reset/:code?', middleware.buildHeader, controllers.reset);
|
||||
app.get('/api/reset/:code?', controllers.reset);
|
||||
|
||||
/* Static Pages */
|
||||
app.get('/404', middleware.buildHeader, controllers.static['404']);
|
||||
app.get('/api/404', controllers.static['404']);
|
||||
|
||||
app.get('/403', middleware.buildHeader, controllers.static['403']);
|
||||
app.get('/api/403', controllers.static['403']);
|
||||
|
||||
app.get('/500', middleware.buildHeader, controllers.static['500']);
|
||||
app.get('/api/500', controllers.static['500']);
|
||||
|
||||
/* Topics */
|
||||
app.get('/topic/:topic_id/:slug?', middleware.buildHeader, controllers.topics.get);
|
||||
app.get('/api/topic/:topic_id/:slug?', controllers.topics.get);
|
||||
|
||||
/* Categories */
|
||||
app.get('/popular/:set?', middleware.buildHeader, controllers.categories.popular);
|
||||
app.get('/api/popular/:set?', controllers.categories.popular);
|
||||
|
||||
app.get('/recent/:term?', middleware.buildHeader, controllers.categories.recent);
|
||||
app.get('/api/recent/:term?', controllers.categories.recent);
|
||||
|
||||
app.get('/unread/', middleware.buildHeader, middleware.authenticate, controllers.categories.unread);
|
||||
app.get('/api/unread/', middleware.authenticate, controllers.categories.unread);
|
||||
|
||||
app.get('/unread/total', middleware.buildHeader, middleware.authenticate, controllers.categories.unreadTotal);
|
||||
app.get('/api/unread/total', middleware.authenticate, controllers.categories.unreadTotal);
|
||||
|
||||
app.get('/category/:category_id/:slug?', middleware.buildHeader, controllers.categories.get);
|
||||
app.get('/api/category/:category_id/:slug?', controllers.categories.get);
|
||||
|
||||
/* Accounts */
|
||||
app.get('/user/:userslug', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getAccount);
|
||||
app.get('/api/user/:userslug', middleware.checkGlobalPrivacySettings, controllers.accounts.getAccount);
|
||||
|
||||
app.get('/user/:userslug/following', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowing);
|
||||
app.get('/api/user/:userslug/following', middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowing);
|
||||
|
||||
app.get('/user/:userslug/followers', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowers);
|
||||
app.get('/api/user/:userslug/followers', middleware.checkGlobalPrivacySettings, controllers.accounts.getFollowers);
|
||||
|
||||
app.get('/user/:userslug/favourites', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.getFavourites);
|
||||
app.get('/api/user/:userslug/favourites', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.getFavourites);
|
||||
|
||||
app.get('/user/:userslug/posts', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.accounts.getPosts);
|
||||
app.get('/api/user/:userslug/posts', middleware.checkGlobalPrivacySettings, controllers.accounts.getPosts);
|
||||
|
||||
app.get('/user/:userslug/edit', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit);
|
||||
app.get('/api/user/:userslug/edit', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountEdit);
|
||||
|
||||
// todo: admin recently gained access to this page, pls check if it actually works
|
||||
app.get('/user/:userslug/settings', middleware.buildHeader, middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings);
|
||||
app.get('/api/user/:userslug/settings', middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, controllers.accounts.accountSettings);
|
||||
|
||||
app.get('/notifications', middleware.buildHeader, middleware.authenticate, controllers.accounts.getNotifications);
|
||||
app.get('/api/notifications', middleware.authenticate, controllers.accounts.getNotifications);
|
||||
|
||||
/* Users */
|
||||
app.get('/users', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers);
|
||||
app.get('/api/users', middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers);
|
||||
|
||||
// was this duped by accident or purpose?
|
||||
app.get('/users/online', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers);
|
||||
app.get('/api/users/online', middleware.checkGlobalPrivacySettings, controllers.users.getOnlineUsers);
|
||||
|
||||
app.get('/users/sort-posts', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByPosts);
|
||||
app.get('/api/users/sort-posts', middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByPosts);
|
||||
|
||||
app.get('/users/sort-reputation', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByReputation);
|
||||
app.get('/api/users/sort-reputation', middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByReputation);
|
||||
|
||||
app.get('/users/latest', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByJoinDate);
|
||||
app.get('/api/users/latest', middleware.checkGlobalPrivacySettings, controllers.users.getUsersSortedByJoinDate);
|
||||
|
||||
app.get('/users/search', middleware.buildHeader, middleware.checkGlobalPrivacySettings, controllers.users.getUsersForSearch);
|
||||
app.get('/api/users/search', middleware.checkGlobalPrivacySettings, controllers.users.getUsersForSearch);
|
||||
|
||||
pluginRoutes(app, middleware, controllers);
|
||||
plugins.fireHook('action:app.load', app, middleware, controllers);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
require('./debug')(app, middleware, controllers);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,72 +1,83 @@
|
||||
"use strict";
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('nconf'),
|
||||
less = require('less'),
|
||||
|
||||
meta = require('../meta'),
|
||||
db = require('../database'),
|
||||
plugins = require('../plugins');
|
||||
meta = require('./../meta'),
|
||||
db = require('./../database'),
|
||||
plugins = require('./../plugins'),
|
||||
|
||||
(function (Meta) {
|
||||
Meta.createRoutes = function(app) {
|
||||
app.get('/stylesheet.css', function(req, res) {
|
||||
if (meta.css.cache) {
|
||||
res.type('text/css').send(200, meta.css.cache);
|
||||
minificationEnabled = false;
|
||||
|
||||
|
||||
function sendMinifiedJS(req, res, next) {
|
||||
function sendCached() {
|
||||
return res.type('text/javascript').send(meta.js.cache);
|
||||
}
|
||||
|
||||
if (meta.js.cache) {
|
||||
sendCached();
|
||||
} else {
|
||||
if (minificationEnabled) {
|
||||
meta.js.minify(function() {
|
||||
sendCached();
|
||||
});
|
||||
} else {
|
||||
// Compress only
|
||||
meta.js.concatenate(function() {
|
||||
sendCached();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendStylesheet(req, res, next) {
|
||||
if (meta.css.cache) {
|
||||
res.type('text/css').send(200, meta.css.cache);
|
||||
return;
|
||||
}
|
||||
|
||||
db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) {
|
||||
var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'),
|
||||
baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')),
|
||||
paths = [baseThemePath, path.join(__dirname, '../../node_modules')],
|
||||
source = '@import "./theme";',
|
||||
x, numLESS, numCSS;
|
||||
|
||||
// Add the imports for each LESS file
|
||||
for(x=0,numLESS=plugins.lessFiles.length;x<numLESS;x++) {
|
||||
source += '\n@import "./' + plugins.lessFiles[x] + '";';
|
||||
}
|
||||
|
||||
// ... and for each CSS file
|
||||
for(x=0,numCSS=plugins.cssFiles.length;x<numCSS;x++) {
|
||||
source += '\n@import (inline) "./' + plugins.cssFiles[x] + '";';
|
||||
}
|
||||
|
||||
var parser = new (less.Parser)({
|
||||
paths: paths
|
||||
});
|
||||
|
||||
parser.parse(source, function(err, tree) {
|
||||
if (err) {
|
||||
res.send(500, err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
db.getObjectFields('config', ['theme:type', 'theme:id'], function(err, themeData) {
|
||||
var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla'),
|
||||
baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')),
|
||||
paths = [baseThemePath, path.join(__dirname, '../../node_modules')],
|
||||
source = '@import "./theme";',
|
||||
x, numLESS;
|
||||
|
||||
// Add the imports for each LESS file
|
||||
for(x=0,numLESS=plugins.lessFiles.length;x<numLESS;x++) {
|
||||
source += '\n@import "./' + plugins.lessFiles[x] + '";';
|
||||
}
|
||||
|
||||
// ... and for each CSS file
|
||||
for(x=0,numCSS=plugins.cssFiles.length;x<numCSS;x++) {
|
||||
source += '\n@import (inline) "./' + plugins.cssFiles[x] + '";';
|
||||
}
|
||||
|
||||
var parser = new (less.Parser)({
|
||||
paths: paths
|
||||
});
|
||||
|
||||
parser.parse(source, function(err, tree) {
|
||||
if (err) {
|
||||
res.send(500, err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
meta.css.cache = tree.toCSS({
|
||||
compress: true
|
||||
});
|
||||
res.type('text/css').send(200, meta.css.cache);
|
||||
});
|
||||
meta.css.cache = tree.toCSS({
|
||||
compress: true
|
||||
});
|
||||
res.type('text/css').send(200, meta.css.cache);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/nodebb.min.js', function(req, res) {
|
||||
var sendCached = function() {
|
||||
return res.type('text/javascript').send(meta.js.cache);
|
||||
}
|
||||
if (meta.js.cache) {
|
||||
sendCached();
|
||||
} else {
|
||||
if (app.enabled('minification')) {
|
||||
meta.js.minify(function() {
|
||||
sendCached();
|
||||
});
|
||||
} else {
|
||||
// Compress only
|
||||
meta.js.concatenate(function() {
|
||||
sendCached();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
})(exports);
|
||||
module.exports = function(app, middleware, controllers) {
|
||||
minificationEnabled = app.enabled('minification');
|
||||
|
||||
app.get('/stylesheet.css', sendStylesheet);
|
||||
app.get('/nodebb.min.js', sendMinifiedJS);
|
||||
app.get('/sitemap.xml', controllers.sitemap);
|
||||
app.get('/robots.txt', controllers.robots);
|
||||
};
|
||||
|
||||
@@ -1,74 +1,199 @@
|
||||
"use strict";
|
||||
|
||||
var nconf = require('nconf'),
|
||||
var _ = require('underscore'),
|
||||
nconf = require('nconf'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
validator = require('validator'),
|
||||
_ = require('underscore'),
|
||||
async = require('async'),
|
||||
plugins = require('../plugins');
|
||||
winston = require('winston'),
|
||||
|
||||
(function(Plugins) {
|
||||
Plugins.createRoutes = function(app) {
|
||||
app.get('/plugins/fireHook', function(req, res) {
|
||||
// GET = filter
|
||||
plugins.fireHook('filter:' + req.query.hook, req.query.args, function(err, returnData) {
|
||||
if (typeof returnData === 'object') {
|
||||
res.json(200, returnData);
|
||||
} else {
|
||||
res.send(200, validator.escape(returnData));
|
||||
plugins = require('../plugins'),
|
||||
pluginRoutes = [];
|
||||
|
||||
/*
|
||||
* TO BE DEPRECATED post 0.4x
|
||||
*/
|
||||
function setupPluginRoutes(app) {
|
||||
var custom_routes = {
|
||||
'routes': [],
|
||||
'api': [],
|
||||
'templates': []
|
||||
};
|
||||
|
||||
plugins.ready(function() {
|
||||
plugins.fireHook('filter:server.create_routes', custom_routes, function(err, custom_routes) {
|
||||
winston.warn('[plugins] filter:server.create_routes is deprecated and will maintain limited functionality until 0.4x');
|
||||
var route,
|
||||
routes = custom_routes.routes;
|
||||
|
||||
pluginRoutes = custom_routes;
|
||||
|
||||
for (route in routes) {
|
||||
if (routes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app[routes[route].method || 'get'](routes[route].route, function(req, res) {
|
||||
routes[route].options(req, res, function(options) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
app.render('header', {}, next);
|
||||
},
|
||||
function(next) {
|
||||
app.render('footer', {}, next);
|
||||
}
|
||||
], function(err, data) {
|
||||
res.send(data[0] + options.content + data[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.put('/plugins/fireHook', function(req, res) {
|
||||
// PUT = action
|
||||
var hook = 'action:' + req.body.hook;
|
||||
if (plugins.hasListeners(hook)) {
|
||||
// Hook executes
|
||||
plugins.fireHook(hook, req.body.args);
|
||||
}
|
||||
|
||||
res.send(200);
|
||||
var apiRoutes = custom_routes.api;
|
||||
for (route in apiRoutes) {
|
||||
if (apiRoutes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app[apiRoutes[route].method || 'get']('/api' + apiRoutes[route].route, function(req, res) {
|
||||
apiRoutes[route].callback(req, res, function(data) {
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
}
|
||||
|
||||
var templateRoutes = custom_routes.templates;
|
||||
for (route in templateRoutes) {
|
||||
if (templateRoutes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app.get('/templates/' + templateRoutes[route].template, function(req, res) {
|
||||
res.send(templateRoutes[route].content);
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Static Assets
|
||||
app.get('/plugins/:id/*', function(req, res) {
|
||||
var relPath = req._parsedUrl.pathname.replace(nconf.get('relative_path') + '/plugins/', ''),
|
||||
matches = _.map(plugins.staticDirs, function(realPath, mappedPath) {
|
||||
if (relPath.match(mappedPath)) {
|
||||
return mappedPath;
|
||||
/*
|
||||
* TO BE DEPRECATED post 0.4x
|
||||
*/
|
||||
function setupPluginAdminRoutes(app) {
|
||||
var custom_routes = {
|
||||
'routes': [],
|
||||
'api': []
|
||||
};
|
||||
|
||||
plugins.ready(function() {
|
||||
plugins.fireHook('filter:admin.create_routes', custom_routes, function(err, custom_routes) {
|
||||
winston.warn('[plugins] filter:admin.create_routes is deprecated and will maintain limited functionality until 0.4x');
|
||||
var route, routes = custom_routes.routes;
|
||||
|
||||
for (route in routes) {
|
||||
if (routes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app[routes[route].method || 'get']('/admin' + routes[route].route, function(req, res) {
|
||||
routes[route].options(req, res, function(options) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
app.render('admin/header', {}, next);
|
||||
},
|
||||
function(next) {
|
||||
app.render('admin/footer', {}, next);
|
||||
}
|
||||
], function(err, data) {
|
||||
res.send(data[0] + options.content + data[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
}
|
||||
|
||||
var apiRoutes = custom_routes.api;
|
||||
for (route in apiRoutes) {
|
||||
if (apiRoutes.hasOwnProperty(route)) {
|
||||
(function(route) {
|
||||
app[apiRoutes[route].method || 'get']('/api/admin' + apiRoutes[route].route, function(req, res) {
|
||||
apiRoutes[route].callback(req, res, function(data) {
|
||||
res.json(data);
|
||||
});
|
||||
});
|
||||
}(route));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(app, middleware, controllers) {
|
||||
/**
|
||||
* GET/PUT /plugins/fireHook to be deprecated after 0.4.x
|
||||
*
|
||||
*/
|
||||
app.get('/plugins/fireHook', function(req, res) {
|
||||
// GET = filter
|
||||
plugins.fireHook('filter:' + req.query.hook, req.query.args, function(err, returnData) {
|
||||
if (typeof returnData === 'object') {
|
||||
res.json(200, returnData);
|
||||
} else {
|
||||
res.send(200, validator.escape(returnData));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.put('/plugins/fireHook', function(req, res) {
|
||||
// PUT = action
|
||||
var hook = 'action:' + req.body.hook;
|
||||
if (plugins.hasListeners(hook)) {
|
||||
// Hook executes
|
||||
plugins.fireHook(hook, req.body.args);
|
||||
}
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
// Static Assets
|
||||
app.get('/plugins/:id/*', function(req, res) {
|
||||
var relPath = req._parsedUrl.pathname.replace(nconf.get('relative_path') + '/plugins/', ''),
|
||||
matches = _.map(plugins.staticDirs, function(realPath, mappedPath) {
|
||||
if (relPath.match(mappedPath)) {
|
||||
return mappedPath;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).filter(function(a) { return a; });
|
||||
|
||||
if (matches) {
|
||||
async.map(matches, function(mappedPath, next) {
|
||||
var filePath = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
|
||||
|
||||
fs.exists(filePath, function(exists) {
|
||||
if (exists) {
|
||||
next(null, filePath);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).filter(function(a) { return a; });
|
||||
|
||||
if (matches) {
|
||||
async.map(matches, function(mappedPath, next) {
|
||||
var filePath = path.join(plugins.staticDirs[mappedPath], decodeURIComponent(relPath.slice(mappedPath.length)));
|
||||
|
||||
fs.exists(filePath, function(exists) {
|
||||
if (exists) {
|
||||
next(null, filePath);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}, function(err, matches) {
|
||||
// Filter out the nulls
|
||||
matches = matches.filter(function(a) {
|
||||
return a;
|
||||
});
|
||||
|
||||
if (matches.length) {
|
||||
res.sendfile(matches[0]);
|
||||
} else {
|
||||
res.redirect('/404');
|
||||
next();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.redirect('/404');
|
||||
}
|
||||
});
|
||||
};
|
||||
})(exports);
|
||||
}, function(err, matches) {
|
||||
// Filter out the nulls
|
||||
matches = matches.filter(function(a) {
|
||||
return a;
|
||||
});
|
||||
|
||||
if (matches.length) {
|
||||
res.sendfile(matches[0]);
|
||||
} else {
|
||||
res.redirect('/404');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.redirect('/404');
|
||||
}
|
||||
});
|
||||
|
||||
setupPluginRoutes(app);
|
||||
setupPluginAdminRoutes(app);
|
||||
};
|
||||
977
src/webserver.js
977
src/webserver.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user