diff --git a/app.js b/app.js index 4324d6f2e4..e8b662bbd4 100644 --- a/app.js +++ b/app.js @@ -96,6 +96,8 @@ global.translator = translator; translator.loadServer(); + + var customTemplates = meta.config['theme:templates'] ? path.join(__dirname, 'node_modules', meta.config['theme:id'], meta.config['theme:templates']) : false; // todo: replace below with read directory code, derp. templates.init([ @@ -104,7 +106,8 @@ 'emails/header', 'emails/footer', 'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic' - ]); + ], customTemplates); + templates.ready(webserver.init); diff --git a/public/src/forum/admin/topics.js b/public/src/forum/admin/topics.js index 8960bed25d..5750e83f37 100644 --- a/public/src/forum/admin/topics.js +++ b/public/src/forum/admin/topics.js @@ -59,6 +59,7 @@ define(function() { topicsListEl.innerHTML += html; btnEl.innerHTML = 'Load More Topics'; + $('span.timeago').timeago(); } else { // Exhausted all topics btnEl.className += ' disabled'; diff --git a/public/src/templates.js b/public/src/templates.js index cb153ed10d..bd3f376ca8 100644 --- a/public/src/templates.js +++ b/public/src/templates.js @@ -57,30 +57,41 @@ return template; }; - function loadTemplates(templatesToLoad) { + function loadTemplates(templatesToLoad, customTemplateDir) { function loadServer() { var loaded = templatesToLoad.length; - for (var t in templatesToLoad) { - (function (file) { - fs.readFile(__dirname + '/../templates/' + file + '.tpl', function (err, html) { - var template = function () { - this.toString = function () { - return this.html; - }; - } - - template.prototype.file = file; - template.prototype.parse = parse; - template.prototype.html = String(html); - - global.templates[file] = new template; - - loaded--; - if (loaded == 0) templates.ready(); - }); - }(templatesToLoad[t])); + function getTemplates(directory) { + for (var t in templatesToLoad) { + (function (file) { + fs.readFile(directory + '/' + file + '.tpl', function (err, html) { + var template = function () { + this.toString = function () { + return this.html; + }; + } + + template.prototype.file = file; + template.prototype.parse = parse; + template.prototype.html = String(html); + + global.templates[file] = new template; + + loaded--; + if (loaded == 0) templates.ready(); + }); + }(templatesToLoad[t])); + } + } + if (customTemplateDir) { + fs.exists(customTemplateDir, function (exists) { + var directory = (exists ? customTemplateDir : __dirname + '/../templates'); + getTemplates(directory); + }); + } else { + getTemplates(__dirname + '/../templates'); } + } function loadClient() { @@ -96,8 +107,8 @@ } - templates.init = function (templates_to_load) { - loadTemplates(templates_to_load || []); + templates.init = function (templates_to_load, custom_templates) { + loadTemplates(templates_to_load || [], custom_templates || false); } templates.getTemplateNameFromUrl = function (url) { @@ -227,6 +238,10 @@ return new RegExp("[\\s\\S]*", 'g'); } + function makeConditionalRegex(block) { + return new RegExp("[\\s\\S]*", 'g'); + } + function getBlock(regex, block, template) { data = template.match(regex); if (data == null) return; @@ -240,6 +255,19 @@ return data; } + function getConditionalBlock(regex, block, template) { + data = template.match(regex); + if (data == null) return; + + if (self.blocks && block !== undefined) self.blocks[block] = data[0]; + + data = data[0] + .replace("", "") + .replace("", ""); + + return data; + } + function setBlock(regex, block, template) { return template.replace(regex, block); } @@ -289,6 +317,14 @@ block = parse(data[d], namespace, block); template = setBlock(regex, block, template); } else { + var conditional = makeConditionalRegex(d), + block = getConditionalBlock(conditional, namespace, template); + + if (block && !data[d]) { + template = template.replace(conditional, ''); + } + + template = replace(namespace + d, data[d], template); } } diff --git a/public/templates/admin/topics.tpl b/public/templates/admin/topics.tpl index 1521f400ac..f970823db8 100644 --- a/public/templates/admin/topics.tpl +++ b/public/templates/admin/topics.tpl @@ -11,7 +11,7 @@ {topics.title}
diff --git a/src/meta.js b/src/meta.js index 90e581c4bb..f82538ea6a 100644 --- a/src/meta.js +++ b/src/meta.js @@ -130,9 +130,8 @@ var utils = require('./../public/src/utils.js'), }); }, function(config, next) { - if (config.staticDir) { - themeData['theme:staticDir'] = config.staticDir; - } + themeData['theme:staticDir'] = config.staticDir ? config.staticDir : ''; + themeData['theme:templates'] = config.templates ? config.templates : ''; RDB.hmset('config', themeData, next); } diff --git a/src/topics.js b/src/topics.js index b40b67eb7c..76408b9fb5 100644 --- a/src/topics.js +++ b/src/topics.js @@ -22,8 +22,12 @@ var RDB = require('./redis.js'), Topics.getTopicData = function(tid, callback) { RDB.hgetall('topic:' + tid, function(err, data) { if (err === null) { - if(data) + if(data) { data.title = validator.sanitize(data.title).escape(); + if(data.timestamp) { + data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString(); + } + } callback(data); } else { @@ -327,8 +331,6 @@ var RDB = require('./redis.js'), topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none'; topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : ''; - topicData.relativeTime = new Date(parseInt(topicData.timestamp, 10)).toISOString(); - topicData.username = topicInfo.username; topicData.badgeclass = (topicInfo.hasread && current_user != 0) ? '' : 'badge-important'; topicData.teaser_text = topicInfo.teaserInfo.text || '', @@ -455,7 +457,6 @@ var RDB = require('./redis.js'), hasRead = results[1], teaser = results[2]; - topicData.relativeTime = new Date(parseInt(topicData.timestamp,10)).toISOString(); topicData.badgeclass = hasRead ? '' : 'badge-important'; topicData.teaser_text = teaser.text || ''; topicData.teaser_username = teaser.username || ''; diff --git a/src/webserver.js b/src/webserver.js index c4502787f0..08dbeab485 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -74,6 +74,7 @@ var express = require('express'), cssSrc: meta.config['theme:src'] || nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css', pluginCSS: plugins.cssFiles.map(function(file) { return { path: file } }), title: meta.config.title || '', + description: meta.config.description || '', 'brand:logo': meta.config['brand:logo'] || '', 'brand:logo:display': meta.config['brand:logo']?'':'hide', browserTitle: meta.config.title || 'NodeBB', @@ -165,7 +166,7 @@ var express = require('express'), }, function(next) { // Theme configuration - RDB.hmget('config', 'theme:type', 'theme:id', 'theme:staticDir', function(err, themeData) { + RDB.hmget('config', 'theme:type', 'theme:id', 'theme:staticDir', 'theme:templates', function(err, themeData) { var themeId = (themeData[1] || 'nodebb-theme-vanilla'); // Detect if a theme has been selected, and handle appropriately @@ -183,6 +184,13 @@ var express = require('express'), } } + if (themeData[3]) { + app.use('/templates', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[3]))); + if (process.env.NODE_ENV === 'development') { + winston.info('Custom templates directory routed for theme: ' + themeData[1]); + } + } + app.use(require('less-middleware')({ src: path.join(__dirname, '../node_modules/' + themeId), dest: path.join(__dirname, '../public/css'),