diff --git a/lib/generate.js b/lib/generate.js index ec244a7d4b..b561ca724e 100644 --- a/lib/generate.js +++ b/lib/generate.js @@ -5,10 +5,11 @@ var util = require('./util'), renderFn = require('./render'), render = renderFn.render, renderFile = renderFn.renderFile, + isRenderable = renderFn.isRenderable, + getOutput = renderFn.getOutput, model = require('./model'), dbAssets = model.assets, extend = require('./extend'), - renderer = extend.renderer.list(), generator = extend.generator.list(), route = require('./route'), i18n = require('./i18n').i18n, @@ -21,6 +22,8 @@ var util = require('./util'), baseDir = hexo.base_dir, sourceDir = hexo.source_dir, themeDir = hexo.theme_dir, + urlConfig = config.url, + rootConfig = config.root, layoutDir = themeDir + 'layout/'; var hiddenFileRegex = /^[^\.](?:(?!\/\.).)*$/, @@ -39,7 +42,7 @@ module.exports = function(options, callback){ }, function(next){ async.forEach(generator, function(item, next){ - item(model, function(layout, locals, callback){ + item(model, function(path, layout, locals){ if (!Array.isArray(layout)) layout = [layout]; var newLocals = { @@ -48,6 +51,8 @@ module.exports = function(options, callback){ config: config, theme: themeConfig, __: themei18n.get, + path: path, + url: urlConfig + rootConfig + path, layout: 'layout', cache: !watch }; @@ -60,9 +65,8 @@ module.exports = function(options, callback){ } } - renderFile(layoutPath, newLocals, function(err, result){ - if (err) throw err; - callback(null, result); + route.set(path, function(fn){ + renderFile(layoutPath, newLocals, fn); }); }, next); }, next); @@ -103,8 +107,8 @@ module.exports = function(options, callback){ }, // Load theme source function(next){ - var sourceDir = themeDir + 'source/', - list = Object.keys(renderer); + var sourceDir = themeDir + 'source/';/*, + list = Object.keys(renderer);*/ fs.exists(sourceDir, function(exist){ if (!exist) return next(); @@ -135,16 +139,10 @@ module.exports = function(options, callback){ dbAssets.insert({source: source.substring(baseDir.length), mtime: mtime}); } - if (list.indexOf(extname) === -1){ - var content = function(fn){ - fn(null, fs.createReadStream(source)); - }; - content.latest = latest; - route.set(item, content); - } else { + if (isRenderable(extname)){ var filename = item.substring(0, item.length - extname.length - 1), fileext = pathFn.extname(filename), - dest = filename + '.' + (fileext ? fileext.substring(1) : renderer[extname].output); + dest = filename + '.' + (fileext ? fileext.substring(1) : getOutput(extname)); var content = function(fn){ render({path: source}, function(err, result){ @@ -154,6 +152,12 @@ module.exports = function(options, callback){ }; content.latest = latest; route.set(dest, content); + } else { + var content = function(fn){ + fn(null, fs.createReadStream(source)); + }; + content.latest = latest; + route.set(item, content); } next(); diff --git a/lib/init.js b/lib/init.js index 4bf8ae46b3..1a03a51894 100644 --- a/lib/init.js +++ b/lib/init.js @@ -12,12 +12,68 @@ var async = require('async'), route = require('./route'), util = require('./util'); +var defaults = { + // Site + title: 'Hexo', + subtitle: '', + description: '', + author: 'John Doe', + email: '', + language: '', + // URL + url: 'http://yoursite.com', + root: '/', + permalink: ':year/:month/:day/:title/', + tag_dir: 'tags', + archive_dir: 'archives', + category_dir: 'categories', + // Writing + new_post_name: ':title.md', + default_layout: 'post', + auto_spacing: false, + titlecase: false, + max_open_file: 100, + filename_case: 0, + highlight: { + enable: true, + backtick_code_block: true, + line_number: true, + tab_replace: '', + }, + // Category & Tag + default_category: 'uncategorized', + category_map: {}, + tag_map: {}, + // Archives + archive: 2, + category: 2, + tag: 2, + // Server + port: 4000, + logger: false, + logger_format: '', + // Date / Time format + date_format: 'MMM D YYYY', + time_format: 'H:mm:ss', + // Pagination + per_page: 10, + pagination_dir: 'page', + // Disqus + disqus_shortname: '', + // Extensions + theme: 'light', + exclude_generator: [], + // Deployment + deploy: {} +}; + module.exports = function(args){ var safe = args.safe ? true : false, debug = args.debug ? true : false, dirname = __dirname, baseDir = process.cwd() + '/', db = new Database(baseDir + 'db.json'), + version = require('../package.json').version, config = {}; var hexo = global.hexo = { @@ -30,7 +86,7 @@ module.exports = function(args){ get scaffold_dir(){return baseDir + 'scaffolds/'}, get core_dir(){return path.dirname(dirname) + '/'}, get lib_dir(){return dirname + '/'}, - get version(){return require('../package.json').version}, + get version(){return version}, get env(){return env}, get safe(){return safe}, get debug(){return debug}, @@ -65,7 +121,7 @@ module.exports = function(args){ if (exist){ render.render({path: configPath}, function(err, result){ if (err) return new Error('Config compiled error'); - config = result; + config = _.extend(defaults, result); Object.freeze(config); next(null, true); }); @@ -74,6 +130,31 @@ module.exports = function(args){ } }); }, + // Update package.json + update: ['config', function(next, results){ + if (!results.config) return next(); + + var packagePath = baseDir + 'package.json'; + + fs.exists(packagePath, function(exist){ + if (exist){ + var obj = require(packagePath); + + if (version === obj.version) return next(); + + obj.version = version; + } else { + var obj = { + name: 'hexo', + version: version, + private: true, + dependencies: {} + }; + } + + fs.writeFile(packagePath, JSON.stringify(obj, null, ' '), next); + }); + }], // Load plugins load_plugins: ['config', function(next, results){ if (safe || !results.config) return next(); diff --git a/lib/model.js b/lib/model.js index 2aa55d7a81..7a8afff9d6 100644 --- a/lib/model.js +++ b/lib/model.js @@ -4,10 +4,10 @@ var moment = require('moment'), config = hexo.config, siteUrl = config.url + '/', sourceDir = hexo.source_dir, - catDir = (config.category_dir || 'categories') + '/', - tagDir = (config.tag_dir || 'tags') + '/', - defaultCategory = config.default_category || 'uncategorized', - configLink = config.permalink || ':year/:month/:day/:title/'; + catDir = config.category_dir + '/', + tagDir = config.tag_dir + '/', + defaultCategory = config.default_category , + configLink = config.permalink; var schemaPosts = new Schema({ id: Number, diff --git a/lib/plugins/console/generate.js b/lib/plugins/console/generate.js index 1157eddf27..6c6d71d0de 100644 --- a/lib/plugins/console/generate.js +++ b/lib/plugins/console/generate.js @@ -11,7 +11,7 @@ var extend = require('../../extend'), config = hexo.config, publicDir = hexo.public_dir, sourceDir = hexo.source_dir, - maxOpenFile = config && config.max_open_file ? config.max_open_file : 100; + maxOpenFile = config.max_open_file; extend.console.register('generate', 'Generate static files', function(args, callback){ var watch = args.w || args.watch ? true : false, diff --git a/lib/plugins/console/index.js b/lib/plugins/console/index.js index d2c9478d44..686f950a31 100644 --- a/lib/plugins/console/index.js +++ b/lib/plugins/console/index.js @@ -6,4 +6,5 @@ require('./deploy'); require('./migrate'); require('./new'); require('./routes'); -require('./version'); \ No newline at end of file +require('./version'); +require('./render'); \ No newline at end of file diff --git a/lib/plugins/console/new.js b/lib/plugins/console/new.js index df9ebc57f8..c0589f3686 100644 --- a/lib/plugins/console/new.js +++ b/lib/plugins/console/new.js @@ -43,7 +43,7 @@ extend.console.register('new', 'Create a new article', function(args, callback){ } var config = hexo.config, - defaultLayout = config.default_layout || 'post', + defaultLayout = config.default_layout, title = args._.pop(), layout = args._.length ? args._[0] : defaultLayout, now = moment(), @@ -53,7 +53,7 @@ extend.console.register('new', 'Create a new article', function(args, callback){ if (layout === 'page'){ target += escape(title) + '/index.md'; } else { - var filename = (config.new_post_name || ':title.md') + var filename = config.new_post_name .replace(':year', now.year()) .replace(':month', now.format('MM')) .replace(':day', now.format('DD')) diff --git a/lib/plugins/console/render.js b/lib/plugins/console/render.js new file mode 100644 index 0000000000..b2db1104f8 --- /dev/null +++ b/lib/plugins/console/render.js @@ -0,0 +1,77 @@ +var extend = require('../../extend'), + renderFn = require('../../render'), + render = renderFn.render, + isRenderable = renderFn.isRenderable, + getOutput = renderFn.getOutput, + util = require('../../util'), + file = util.file, + fs = require('graceful-fs'), + pathFn = require('path'), + _ = require('lodash'), + async = require('async'); + +extend.console.register('render', 'Render file', {init: true}, function(args, callback){ + var baseDir = hexo.base_dir, + outputDir = args.o || args.output || baseDir; + + if (!args._.length){ + console.log('No input files.'); + return callback(); + } + + var resolve = function(path){ + return pathFn.resolve(outputDir, path); + }; + + var getItemPath = function(path){ + var extname = pathFn.extname(path); + return resolve(path.substring(0, path.length - extname.length + 1) + getOutput(extname)); + }; + + var renderItem = function(path, callback){ + if (!isRenderable(path)) return callback(); + + render({path: path}, args, function(err, result){ + if (err) throw err; + if (!result) return callback(); + + if (_.isObject(result)) result = JSON.stringify(result, null, ' '); + + fs.writeFile(getItemPath(path), result, callback); + }); + }; + + async.forEach(args._, function(item, next){ + item = item.toString(); + + async.waterfall([ + function(cb){ + fs.exists(item, function(exist){ + if (!exist) return next(); + cb(); + }); + }, + function(cb){ + fs.stat(item, function(err, stats){ + if (err) throw err; + + cb(null, stats.isDirectory()); + }); + }, + function(isDirectory, cb){ + if (isDirectory){ + file.dir(item, function(files){ + async.forEach(files, function(i, cb){ + renderItem(item + '/' + i, cb); + }, next); + }); + } else { + renderItem(item, next); + } + } + ]); + }, function(){ + console.log('Render complete.'); + callback(); + }); +}); \ No newline at end of file diff --git a/lib/plugins/console/server.js b/lib/plugins/console/server.js index 5fd15cf2b6..b8a42964cc 100644 --- a/lib/plugins/console/server.js +++ b/lib/plugins/console/server.js @@ -65,6 +65,7 @@ extend.console.register('server', 'Run server', function(args){ var startServer = function(){ app.use(config.root, express.static(publicDir)); + if (hexo.debug) app.use(express.errorHandler()); if (config.root !== '/'){ app.get('/', function(req, res){ @@ -144,13 +145,13 @@ extend.console.register('server', 'Run server', function(args){ target = route.get(uri); if (!target){ - if (uri.substr(uri.length - 1, 1) === '/') return next(); - res.redirect(req.url + '/'); + if (/\/(index\.html?)?$/.test(uri)) return next(); + res.redirect(uri + '/'); return; } target(function(err, result){ - if (err) throw new Error('Route Error: ' + uri); + if (err) throw err; res.type(path.extname(uri)); diff --git a/lib/plugins/generator/archive.js b/lib/plugins/generator/archive.js index b631a7be01..5fdcfaaba0 100644 --- a/lib/plugins/generator/archive.js +++ b/lib/plugins/generator/archive.js @@ -18,9 +18,7 @@ extend.generator.register(function(locals, render, callback){ if (mode == 2){ paginator(path, posts, 'archive', render); } else { - route.set(path, function(fn){ - render(['archive', 'index'], posts, fn); - }); + render(path, ['archive', 'index'], posts); } }; @@ -36,21 +34,23 @@ extend.generator.register(function(locals, render, callback){ // Yearly for (var i=oldest.year(); i<=newest.year(); i++){ var yearly = posts.find({date: {$lt: new Date(i + 1, 0, 1), $gte: new Date(i, 0, 1)}}); - if (yearly.length){ - yearly.year = i; - yearly.archive = true; - generate(archiveDir + i + '/', yearly); - - // Monthly - for (var j=1; j<=12; j++){ - var monthly = yearly.find({date: {$lt: new Date(i, j, 1), $gte: new Date(i, j - 1, 1)}}); - if (monthly.length){ - monthly.year = i; - monthly.month = j; - monthly.archive = true; - generate(archiveDir + i + '/' + (j < 10 ? '0' + j : j) + '/', monthly); - } - } + + if (!yearly.length) continue; + + yearly.year = i; + yearly.archive = true; + generate(archiveDir + i + '/', yearly); + + // Monthly + for (var j=1; j<=12; j++){ + var monthly = yearly.find({date: {$lt: new Date(i, j, 1), $gte: new Date(i, j - 1, 1)}}); + + if (!monthly.length) continue; + + monthly.year = i; + monthly.month = j; + monthly.archive = true; + generate(archiveDir + i + '/' + (j < 10 ? '0' + j : j) + '/', monthly); } } diff --git a/lib/plugins/generator/category.js b/lib/plugins/generator/category.js index 1aff909a9e..6377a1b787 100644 --- a/lib/plugins/generator/category.js +++ b/lib/plugins/generator/category.js @@ -14,6 +14,8 @@ extend.generator.register(function(locals, render, callback){ } locals.categories.each(function(cat){ + if (!cat.length) return; + var posts = cat.posts.sort('date', -1), path = cat.path; @@ -22,9 +24,7 @@ extend.generator.register(function(locals, render, callback){ if (config == 2){ paginator(path, posts, 'category', render); } else { - route.set(path, function(fn){ - render(['category', 'archive', 'index'], posts, fn); - }); + render(path, ['category', 'archive', 'index'], posts); } }); diff --git a/lib/plugins/generator/page.js b/lib/plugins/generator/page.js index ba27109221..42196c80c8 100644 --- a/lib/plugins/generator/page.js +++ b/lib/plugins/generator/page.js @@ -4,10 +4,7 @@ var route = require('../../route'), extend.generator.register(function(locals, render, callback){ locals.pages.each(function(item){ var layout = item.layout; - - route.set(item.path, function(fn){ - render([layout, 'page', 'index'], item, fn); - }); + render(item.path, [layout, 'page', 'index'], item); }); callback(); diff --git a/lib/plugins/generator/paginator.js b/lib/plugins/generator/paginator.js index 65dca0e543..d5bfbda868 100644 --- a/lib/plugins/generator/paginator.js +++ b/lib/plugins/generator/paginator.js @@ -48,14 +48,10 @@ module.exports = function(base, posts, layout, render){ var paginator = new Paginator(base, posts, i, total), path = paginator.current_url; - route.set(path, function(fn){ - render([layout, 'archive', 'index'], paginator, fn); - }); + render(path, [layout, 'archive', 'index'], paginator); })(i); } } else { - route.set(base, function(fn){ - render([layout, 'archive', 'index'], {posts: posts}, fn); - }); + render(base, [layout, 'archive', 'index'], {posts: posts}); } }; \ No newline at end of file diff --git a/lib/plugins/generator/post.js b/lib/plugins/generator/post.js index 235eb43b83..addf4f4518 100644 --- a/lib/plugins/generator/post.js +++ b/lib/plugins/generator/post.js @@ -9,9 +9,7 @@ extend.generator.register(function(locals, render, callback){ layout = item.layout; item.prev = i === 0 ? null : arr[i - 1]; item.next = i === len - 1 ? null : arr[i + 1]; - route.set(item.path, function(fn){ - render([layout, 'post', 'index'], item, fn); - }); + render(item.path, [layout, 'post', 'index'], item); })(i); } diff --git a/lib/plugins/generator/tag.js b/lib/plugins/generator/tag.js index b0981d2528..d6eaa51d72 100644 --- a/lib/plugins/generator/tag.js +++ b/lib/plugins/generator/tag.js @@ -14,6 +14,8 @@ extend.generator.register(function(locals, render, callback){ } locals.tags.each(function(tag){ + if (!tag.length) return; + var posts = tag.posts.sort('date', -1), path = tag.path; @@ -22,9 +24,7 @@ extend.generator.register(function(locals, render, callback){ if (config == 2){ paginator(path, posts, 'tag', render); } else { - route.set(path, function(fn){ - render(['tag', 'archive', 'index'], posts, fn); - }); + render(path, ['tag', 'archive', 'index'], posts); } }); diff --git a/lib/plugins/helper/css.js b/lib/plugins/helper/css.js index a404d3d091..619db9fc1a 100644 --- a/lib/plugins/helper/css.js +++ b/lib/plugins/helper/css.js @@ -1,4 +1,5 @@ -var extend = require('../../extend'); +var extend = require('../../extend'), + root = hexo.config.root; extend.helper.register('css', function(path){ if (!Array.isArray) path = [path]; @@ -6,6 +7,9 @@ extend.helper.register('css', function(path){ var result = []; path.forEach(function(item){ + if (item.substr(item.length - 4, 4) !== '.css') item += '.css'; + if (item.substr(0, 1) !== '/') item += root; + result.push(''); }); diff --git a/lib/plugins/helper/date.js b/lib/plugins/helper/date.js new file mode 100644 index 0000000000..411e947f41 --- /dev/null +++ b/lib/plugins/helper/date.js @@ -0,0 +1,43 @@ +var extend = require('../../extend'), + moment = require('moment'), + isMoment = moment.isMoment, + config = hexo.config, + dateFormat = config.date_format, + timeFormat = config.time_format, + fullFormat = dateFormat + ' ' + timeFormat; + +var result = function(date, format){ + if (isMoment(date)){ + return date.format(format); + } else { + return moment(date).format(format); + } +}; + +var toISOString = function(date){ + if (isMoment(date)){ + return date.toDate().toISOString(); + } else { + return date.toISOString(); + } +}; + +extend.helper.register('date', function(date, format){ + return result(date, format || dateFormat); +}); + +extend.helper.register('date_xml', toISOString); + +extend.helper.register('time', function(date, format){ + return result(date, format || timeFormat); +}); + +extend.helper.register('full_date', function(date, format){ + return result(date, format || fullFormat); +}); + +extend.helper.register('time_tag', function(date, format){ + return ''; +}); + +extend.helper.register('moment', moment); \ No newline at end of file diff --git a/lib/plugins/helper/form.js b/lib/plugins/helper/form.js new file mode 100644 index 0000000000..328fd10f88 --- /dev/null +++ b/lib/plugins/helper/form.js @@ -0,0 +1,19 @@ +var extend = require('../../extend'), + _ = require('lodash'), + urlConfig = hexo.config.url; + +extend.helper.register('search_form', function(options){ + var defaults = { + class: 'search-form', + text: 'Search', + button: false + }; + + var options = _.extend(defaults, options); + + return '
' + + '' + + (options.button ? '' : '') + + '' + + '
'; +}); \ No newline at end of file diff --git a/lib/plugins/helper/format.js b/lib/plugins/helper/format.js new file mode 100644 index 0000000000..d59b337e74 --- /dev/null +++ b/lib/plugins/helper/format.js @@ -0,0 +1,37 @@ +var extend = require('../../extend'), + util = require('../../util'), + renderSync = require('../../render').renderSync; + +extend.helper.register('strip_html', function(content){ + return content.toString().replace(/<[^>]*>/g, ''); +}); + +extend.helper.register('trim', function(content){ + return content.toString().trim(); +}); + +extend.helper.register('titlecase', util.titlecase); + +extend.helper.register('markdown', function(text){ + return renderSync({text: text, engine: 'markdown'}); +}); + +extend.helper.register('word_wrap', function(text, width){ + if (!width) width = 80; + + var arr = []; + + for (var i = 0, length = text.length; i < length; i += width){ + arr.push(text.substr(i, width)); + } + + return arr.join('\n'); +}); + +extend.helper.register('truncate', function(text, length){ + return text.substring(0, length); +}); + +extend.helper.register('truncate_words', function(text, length){ + return text.split(' ').slice(0, length).join(' '); +}); \ No newline at end of file diff --git a/lib/plugins/helper/index.js b/lib/plugins/helper/index.js index 6ec47a712c..8b9cfbc8c1 100644 --- a/lib/plugins/helper/index.js +++ b/lib/plugins/helper/index.js @@ -1,10 +1,13 @@ require('./css'); require('./js'); -require('./markdown'); -require('./trim'); -require('./strip'); require('./partial'); -require('./titlecase'); require('./tagcloud'); require('./gravatar'); -require('./paginator'); \ No newline at end of file +require('./paginator'); +require('./is'); +require('./list'); +require('./date'); +require('./link'); +require('./format'); +require('./number'); +require('./form'); \ No newline at end of file diff --git a/lib/plugins/helper/is.js b/lib/plugins/helper/is.js new file mode 100644 index 0000000000..b7a45ad8ec --- /dev/null +++ b/lib/plugins/helper/is.js @@ -0,0 +1,57 @@ +var extend = require('../../extend'), + config = hexo.config, + archiveDir = config.archive_dir, + categoryDir = config.category_dir, + tagDir = config.tag_dir, + pageDir = config.pagination_dir, + permalink = config.permalink; + +var regexPostStr = permalink + .replace(/\//g, '\\/') + .replace(':id', '\\d+') + .replace(':category', '(\\w+\\/?/)+') + .replace(':year', '\\d{4}') + .replace(/:(month|day)/g, '\\d{2}') + .replace(':title', '[^\\/]+'); + +var regex = { + home: new RegExp('^' + pageDir + '\\/\\d+\\/'), + post: new RegExp('^' + regexPostStr), + archive: new RegExp('^' + archiveDir + '\\/'), + year: new RegExp('^' + archiveDir + '\\/\\d{4}\\/'), + month: new RegExp('^' + archiveDir + '\\/\\d{4}\\/\\d{2}\\/'), + category: new RegExp('^' + categoryDir + '\\/'), + tag: new RegExp('^' + tagDir + '\\/') +}; + +extend.helper.register('is_current', function(path){ + return this.path === path; +}); + +extend.helper.register('is_home', function(){ + return this.path === '' || regex.home.test(this.path); +}); + +extend.helper.register('is_post', function(){ + return regex.post.test(this.path); +}); + +extend.helper.register('is_archive', function(){ + return regex.archive.test(this.path); +}); + +extend.helper.register('is_year', function(){ + return regex.year.test(this.path); +}); + +extend.helper.register('is_month', function(){ + return regex.month.test(this.path); +}); + +extend.helper.register('is_category', function(){ + return regex.category.test(this.path); +}); + +extend.helper.register('is_tag', function(){ + return regex.tag.test(this.path); +}); \ No newline at end of file diff --git a/lib/plugins/helper/js.js b/lib/plugins/helper/js.js index c2f15dcb6f..7377f9c2ee 100644 --- a/lib/plugins/helper/js.js +++ b/lib/plugins/helper/js.js @@ -1,4 +1,5 @@ -var extend = require('../../extend'); +var extend = require('../../extend'), + root = hexo.config.root; extend.helper.register('js', function(path){ if (!Array.isArray) path = [path]; @@ -6,6 +7,9 @@ extend.helper.register('js', function(path){ var result = []; path.forEach(function(item){ + if (item.substr(item.length - 3, 3) !== '.js') item += '.js'; + if (item.substr(0, 1) !== '/') item += root; + result.push(''); }); diff --git a/lib/plugins/helper/link.js b/lib/plugins/helper/link.js new file mode 100644 index 0000000000..ba6945cbe3 --- /dev/null +++ b/lib/plugins/helper/link.js @@ -0,0 +1,9 @@ +var extend = require('../../extend'); + +extend.helper.register('link_to', function(path, text, external){ + return '' + (text ? text : path) + ''; +}); + +extend.helper.register('mail_to', function(path, text){ + return '' + (text ? text : path) + ''; +}); \ No newline at end of file diff --git a/lib/plugins/helper/list.js b/lib/plugins/helper/list.js new file mode 100644 index 0000000000..277fb672a3 --- /dev/null +++ b/lib/plugins/helper/list.js @@ -0,0 +1,93 @@ +var extend = require('../../extend'), + _ = require('lodash'), + moment = require('moment'), + root = hexo.config.root, + archiveDir = hexo.config.archive_dir; + +var result = function(prefix, obj, options){ + var defaults = { + orderby: 'name', + order: 1, + show_count: true + }; + + var options = _.extend(defaults, options); + + var result = ''; +}; + +extend.helper.register('list_categories', function(options){ + return result('category', this.site.categories, options); +}); + +extend.helper.register('list_tags', function(options){ + return result('tag', this.site.tags, options); +}); + +extend.helper.register('list_archives', function(options){ + var defaults = { + type: 'monthly', + order: 1, + show_count: true, + format: 'MMMM YYYY' + }; + + var options = _.extend(defaults, options); + + var type = options.type, + order = options.order, + format = options.format, + showCount = options.show_count, + arr = []; + + var posts = this.site.posts.sort('date'), + newest = posts.last().date, + oldest = posts.first().date; + + for (var i = oldest.year(); i <= newest.year(); i++){ + var yearly = posts.find({date: {$lt: new Date(i + 1, 0, 1), $gte: new Date(i, 0, 1)}}); + if (!yearly.length) continue; + + if (type === 'yearly'){ + arr.push('
  • ' + + '' + moment([i]).format(format) + '' + + (showCount ? '' + yearly.length + '' : '') + + '
  • '); + continue; + } + + for (var j = 1; j <= 12; j++){ + var monthly = yearly.find({date: {$lt: new Date(i, j, 1), $gte: new Date(i, j - 1, 1)}}); + if (!monthly.length) continue; + + arr.push('
  • ' + + '' + moment([i, j - 1]).format(format) + '' + + (showCount ? '' + monthly.length + '' : '') + + '
  • '); + } + } + + if (order == -1 || order.toString().toLowerCase() === 'desc'){ + arr = arr.reverse(); + } + + return ''; +}); \ No newline at end of file diff --git a/lib/plugins/helper/markdown.js b/lib/plugins/helper/markdown.js deleted file mode 100644 index 7cd41cc05f..0000000000 --- a/lib/plugins/helper/markdown.js +++ /dev/null @@ -1,6 +0,0 @@ -var extend = require('../../extend'), - renderSync = require('../../render').renderSync; - -extend.helper.register('markdown', function(text){ - return renderSync({text: text, engine: 'markdown'}); -}); \ No newline at end of file diff --git a/lib/plugins/helper/number.js b/lib/plugins/helper/number.js new file mode 100644 index 0000000000..76e34e524a --- /dev/null +++ b/lib/plugins/helper/number.js @@ -0,0 +1,53 @@ +var extend = require('../../extend'), + _ = require('lodash'); + +extend.helper.register('number_format', function(num, options){ + var defaults = { + precision: false, + delimiter: ',', + separator: '.' + }; + + var num = num.toString().split('.'), + options = _.extend(defaults, options); + + var before = num.shift(), + after = num.length ? num[0] : '', + delimiter = options.delimiter, + precision = options.precision; + + if (delimiter){ + var beforeArr = [], + beforeLength = before.length, + beforeFirst = beforeLength % 3; + + if (beforeFirst) beforeArr.push(before.substr(0, beforeFirst)); + + for (var i = beforeFirst; i < beforeLength; i += 3){ + beforeArr.push(before.substr(i, 3)); + } + + before = beforeArr.join(delimiter); + } + + if (precision){ + var afterLength = after.length, + afterResult = ''; + + if (afterLength > precision){ + var afterLast = after[precision], + last = parseInt(after[precision - 1]); + + afterResult = after.substr(0, precision - 1) + (afterLast < 5 ? last : last + 1); + } else { + afterResult = after; + for (var i = 0, len = precision - afterLength; i < len; i++){ + afterResult += '0'; + } + } + + after = afterResult; + } + + return before + (after ? options.separator + after : ''); +}); \ No newline at end of file diff --git a/lib/plugins/helper/paginator.js b/lib/plugins/helper/paginator.js index 153ec02b52..a524a99250 100644 --- a/lib/plugins/helper/paginator.js +++ b/lib/plugins/helper/paginator.js @@ -2,21 +2,21 @@ var extend = require('../../extend'), _ = require('lodash'), config = hexo.config; -var defaults = { - base: '/', - format: config.pagination_dir + '/%d/', - total: 1, - current: 0, - prev_text: 'Prev', - next_text: 'Next', - space: '…', - prev_next: true, - end_size: 1, - mid_size: 2, - show_all: false -}; - extend.helper.register('paginator', function(options){ + var defaults = { + base: '/', + format: config.pagination_dir + '/%d/', + total: 1, + current: 0, + prev_text: 'Prev', + next_text: 'Next', + space: '…', + prev_next: true, + end_size: 1, + mid_size: 2, + show_all: false + }; + var options = _.extend(defaults, options), current = options.current, total = options.total, diff --git a/lib/plugins/helper/partial.js b/lib/plugins/helper/partial.js index 9ab67bfff2..241445d7cd 100644 --- a/lib/plugins/helper/partial.js +++ b/lib/plugins/helper/partial.js @@ -16,10 +16,16 @@ var render = function(view, options){ if (this.cache && cache.hasOwnProperty(source)){ var content = cache[source]; } else { - var content = file.readSync(source); - if (this.cache) cache[source] = content; + try { + var content = file.readSync(source); + if (this.cache) cache[source] = content; + } catch (err) { + if (hexo.debug) throw err; + } } + if (!content) return ''; + return renderSync({text: content, path: source}, _.extend(_.clone(this), options, {filename: source})); }; diff --git a/lib/plugins/helper/strip.js b/lib/plugins/helper/strip.js deleted file mode 100644 index 5197e473aa..0000000000 --- a/lib/plugins/helper/strip.js +++ /dev/null @@ -1,5 +0,0 @@ -var extend = require('../../extend'); - -extend.helper.register('strip_html', function(content){ - return content.replace(/<[^>]*>/g, ''); -}); \ No newline at end of file diff --git a/lib/plugins/helper/tagcloud.js b/lib/plugins/helper/tagcloud.js index efaa93c991..26f9da8296 100644 --- a/lib/plugins/helper/tagcloud.js +++ b/lib/plugins/helper/tagcloud.js @@ -1,8 +1,14 @@ var extend = require('../../extend'), _ = require('lodash'), - config = hexo.config; + config = hexo.config, + root = config.root; extend.helper.register('tagcloud', function(tags, options){ + if (!options){ + options = tags; + tags = this.site.tags; + } + var defaults = { min_font: 10, max_font: 20, @@ -18,23 +24,29 @@ extend.helper.register('tagcloud', function(tags, options){ var options = _.extend(defaults, options), raw = {}, - result = ''; + result = '', + amount = options.amount, + minFont = options.min_font, + maxFont = options.max_font, + unit = options.unit, + orderby = options.orderby.toLowerCase(), + order = options.order; - if (options.amount) tags = tags.limit(options.amount); + if (amount) tags = tags.limit(amount); var length = tags.length, tags = tags.sort('length'); for (var i=0; i' + item.name + ''; + size = minFont + (maxFont - minFont) / length * i; + raw[item.name] = '' + item.name + ''; } - if (options.orderby === 'random' || options.orderby === 'rand'){ + if (orderby === 'random' || orderby === 'rand'){ tags = tags.random(); } else { - tags = tags.sort(options.orderby, options.order); + tags = tags.sort(orderby, order); } tags.each(function(item){ diff --git a/lib/plugins/helper/titlecase.js b/lib/plugins/helper/titlecase.js deleted file mode 100644 index 23084edfa1..0000000000 --- a/lib/plugins/helper/titlecase.js +++ /dev/null @@ -1,4 +0,0 @@ -var extend = require('../../extend'), - util = require('../../util'); - -extend.helper.register('titlecase', util.titlecase); \ No newline at end of file diff --git a/lib/plugins/helper/trim.js b/lib/plugins/helper/trim.js deleted file mode 100644 index 57287af9fe..0000000000 --- a/lib/plugins/helper/trim.js +++ /dev/null @@ -1,5 +0,0 @@ -var extend = require('../../extend'); - -extend.helper.register('trim', function(content){ - return content.toString().trim(); -}); \ No newline at end of file diff --git a/lib/plugins/processor/index.js b/lib/plugins/processor/index.js index 8e6784146a..13f0fe76fe 100644 --- a/lib/plugins/processor/index.js +++ b/lib/plugins/processor/index.js @@ -5,9 +5,10 @@ var fs = require('graceful-fs'), _ = require('lodash'), async = require('async'), extend = require('../../extend'), - renderer = Object.keys(extend.renderer.list()), tagExt = extend.tag.list(), - render = require('../../render'), + renderFn = require('../../render'), + render = renderFn.render, + isRenderable = renderFn.isRenderable, route = require('../../route'), model = require('../../model'), dbPosts = model.posts, @@ -21,15 +22,15 @@ var fs = require('graceful-fs'), highlight = util.highlight, config = hexo.config, highlightConfig = config.highlight, - highlightEnable = highlightConfig ? highlightConfig.enable : true, - backtickConfig = highlightConfig ? highlightConfig.backtick_code_block : true, - lineNumConfig = highlightConfig ? highlightConfig.line_number : true, - tabConfig = highlightConfig ? highlightConfig.tab_replace : '', + highlightEnable = highlightConfig.enable, + backtickConfig = highlightConfig.backtick_code_block, + lineNumConfig = highlightConfig.line_number, + tabConfig = highlightConfig.tab_replace, autoSpacingConfig = config.auto_spacing, titlecaseConfig = config.titlecase, newPostConfig = config.new_post_name, - categoryMap = config.category_map || {}, - tagMap = config.tag_map || {}, + categoryMap = config.category_map, + tagMap = config.tag_map, filenameCaps = config.filename_case, excerptRegex = //; @@ -248,7 +249,7 @@ extend.processor.register(/^_posts\/([^_](?:(?!\/_).)*)$/, function(file, callba } // Exit if the file can't be rendered - if (renderer.indexOf(extname) === -1) return callback(); + if (!isRenderable(extname)) return callback(); async.auto({ // Get file status @@ -374,40 +375,7 @@ extend.processor.register(/^[^_](?:(?!\/_).)*$/, function(file, callback){ extname = pathFn.extname(path).substring(1), type = file.type; - // Normal file - if (renderer.indexOf(extname) === -1){ - var data = dbAssets.findOne({source: 'source/' + path}); - - if (type === 'delete' && data){ - route.remove(path); - dbAssets.remove(data._id); - return callback(); - } - - file.stat(function(err, stat){ - if (err) throw new Error('File status read error: ' + source); - - var content = function(fn){ - fn(null, fs.createReadStream(source)); - }; - - var mtime = stat.mtime; - - if (data){ - if (mtime.getTime() === data.mtime.getTime()){ - content.latest = true; - } else { - dbAssets.update(data._id, {mtime: mtime}); - } - } else { - dbAssets.insert({source: 'source/' + path, mtime: mtime}); - } - - route.set(path, content); - callback(); - }); - // Markdown - } else { + if (isRenderable(extname)){ var data = dbPages.findOne({source: path}); if (type === 'delete' && data){ @@ -463,6 +431,37 @@ extend.processor.register(/^[^_](?:(?!\/_).)*$/, function(file, callback){ existed.pages.push(_id); callback(); }); + } else { + var data = dbAssets.findOne({source: 'source/' + path}); + + if (type === 'delete' && data){ + route.remove(path); + dbAssets.remove(data._id); + return callback(); + } + + file.stat(function(err, stat){ + if (err) throw new Error('File status read error: ' + source); + + var content = function(fn){ + fn(null, fs.createReadStream(source)); + }; + + var mtime = stat.mtime; + + if (data){ + if (mtime.getTime() === data.mtime.getTime()){ + content.latest = true; + } else { + dbAssets.update(data._id, {mtime: mtime}); + } + } else { + dbAssets.insert({source: 'source/' + path, mtime: mtime}); + } + + route.set(path, content); + callback(); + }); } }); diff --git a/lib/plugins/tag/code.js b/lib/plugins/tag/code.js index 148988ce3a..1a49db8018 100644 --- a/lib/plugins/tag/code.js +++ b/lib/plugins/tag/code.js @@ -4,8 +4,8 @@ var extend = require('../../extend'), highlight = require('../../util').highlight, config = hexo.config, highlightConfig = config.highlight, - lineNumConfig = highlightConfig ? highlightConfig.line_number : true, - tabConfig = highlightConfig ? highlightConfig.tab_replace : ''; + lineNumConfig = highlightConfig.line_number, + tabConfig = highlightConfig.tab_replace; var regex = { captionUrlTitle: /(\S[\S\s]*)\s+(https?:\/\/)(\S+)\s+(.+)/i, diff --git a/lib/plugins/tag/pullquote.js b/lib/plugins/tag/pullquote.js index 7fb280dbc9..23f7ba527c 100644 --- a/lib/plugins/tag/pullquote.js +++ b/lib/plugins/tag/pullquote.js @@ -1,7 +1,8 @@ -var extend = require('../../extend'); +var extend = require('../../extend'), + renderSync = require('../../render').renderSync; extend.tag.register('pullquote', function(args, content){ var className = args.length ? ' ' + args.join(' ') : ''; - return '
    ' + content + '
    '; + return '
    ' + renderSync({text: content, engine: 'markdown'}) + '
    '; }, true); \ No newline at end of file diff --git a/lib/render.js b/lib/render.js index f7e2d1a2d6..5f68f10043 100644 --- a/lib/render.js +++ b/lib/render.js @@ -10,6 +10,27 @@ var renderer = require('./extend').renderer.list(), yfm = util.yfm, cache = {}; +var getExtname = function(str){ + var extname = path.extname(str); + if (extname) str = extname; + + if (str[0] === '.') str = str.substring(1); + + return str; +}; + +var isRenderable = exports.isRenderable = function(str){ + return renderer.hasOwnProperty(getExtname(str)); +}; + +var isRenderableSync = exports.isRenderableSync = function(str){ + return rendererSync.hasOwnProperty(getExtname(str)); +}; + +var getOutput = exports.getOutput = function(str){ + if (isRenderable(str)) return renderer[getExtname(str)].output; +}; + var render = exports.render = function(data, options, callback){ if (_.isFunction(options)){ callback = options; @@ -26,6 +47,8 @@ var render = exports.render = function(data, options, callback){ } else if (source){ file.read(source, function(err, content){ if (err) return callback(err); + if (!content) return callback(); + text = content; next(); }); @@ -36,7 +59,7 @@ var render = exports.render = function(data, options, callback){ function(next){ var ext = data.engine || path.extname(data.path).substring(1); - if (ext && renderer.hasOwnProperty(ext)){ + if (ext && isRenderable(ext)){ renderer[ext]({path: source, text: text}, options, callback); } else { callback(null, text); @@ -50,13 +73,14 @@ var renderSync = exports.renderSync = function(data, options){ var text = data.text; } else if (data.path){ var text = file.readSync(data.path); + if (!text) return; } else { return; } var ext = data.engine || path.extname(data.path).substring(1); - if (ext && rendererSync.hasOwnProperty(ext)){ + if (ext && isRenderableSync(ext)){ return rendererSync[ext]({path: data.path, text: text}, options); } else { return text; @@ -89,14 +113,20 @@ var renderFile = exports.renderFile = function(source, options, callback){ var layout = data.layout || options.layout, content = data._content; - renderer[ext]({path: source, text: content}, _.extend(options, helper), function(err, result){ + delete data.layout; + delete data._content; + + renderer[ext]({path: source, text: content}, _.extend(options, helper, data), function(err, result){ if (err) return callback(err); if (!layout) return callback(null, result); + var layoutPath = path.dirname(source) + '/' + layout; if (!path.extname(layoutPath)) layoutPath += '.' + ext; + fs.exists(layoutPath, function(exist){ if (!exist) return callback(null, result); - renderFile(layoutPath, _.extend(options, {body: result, layout: false}), callback); + + renderFile(layoutPath, _.extend(_.clone(options), {body: result, layout: false}), callback); }); }); } diff --git a/lib/util/yfm.js b/lib/util/yfm.js index f5cc516bfe..98d7a29ea1 100644 --- a/lib/util/yfm.js +++ b/lib/util/yfm.js @@ -1,4 +1,4 @@ -var yaml = require('yamljs'); +var render = require('../render').renderSync; module.exports = function(source){ var content = source.replace(/^-{3}/, '').split('---'); @@ -6,7 +6,7 @@ module.exports = function(source){ if (content.length === 1){ var result = {_content: content[0]}; } else { - var result = yaml.parse(content.shift()); + var result = render({text: content.shift(), engine: 'yaml'}); result._content = content.join('---'); }