diff --git a/index.js b/index.js
index e707bbc5..0f376ea3 100644
--- a/index.js
+++ b/index.js
@@ -46,6 +46,21 @@ var cache = {};
*/
var defaultTemplate = join(__dirname, 'public', 'directory.html');
+var templates = {
+ html: {
+ list: '
',
+ header: '',
+ item: ''
+ + '{file.name}'
+ + '{file.size}'
+ + '{file.lastModified}'
+ + ''
+ }
+}
/*!
* Stylesheet.
@@ -154,13 +169,51 @@ function serveIndex(root, options) {
});
files.sort();
+ // add parent directory as first
+ if (showUp) {
+ files.unshift('..');
+ }
+
// content-negotiation
var accept = accepts(req);
var type = accept.type(mediaTypes);
// not acceptable
if (!type) return next(createError(406));
- serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
+
+ // stat all files
+ fstat(path, files, function (err, stats) {
+ if (err) return next(err);
+
+ // combine the stats into the file list
+ var fileList = files.map(function (file, i) {
+ return { name: file, stat: stats[i] };
+ });
+
+ // sort file list
+ fileList.sort(fileSort);
+
+ // make similar to file object (with stat)
+ var directory = {
+ name: originalDir,
+ type: 'inode/directory',
+ size: stat.size,
+ lastModified: stat.mtime
+ }
+
+ var nodes = fileList.map(function (file) {
+ var ext = extname(file.name)
+ var mimetype = mime.lookup(ext)
+ return {
+ name: file.name,
+ type: file.stat.isDirectory() ? 'inode/directory' : mimetype,
+ size: file.stat.size,
+ lastModified: file.stat.mtime
+ }
+ })
+
+ serveIndex[mediaType[type]](req, res, directory, nodes, next, showUp, icons, path, view, template, stylesheet)
+ });
});
});
};
@@ -170,46 +223,29 @@ function serveIndex(root, options) {
* Respond with text/html.
*/
-serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
+serveIndex.html = function _html(req, res, directory, files, next, showUp, icons, path, view, template, stylesheet) {
var render = typeof template !== 'function'
? createHtmlRender(template)
: template
- if (showUp) {
- files.unshift('..');
- }
-
- // stat all files
- stat(path, files, function (err, stats) {
+ // read stylesheet
+ fs.readFile(stylesheet, 'utf8', function (err, style) {
if (err) return next(err);
- // combine the stats into the file list
- var fileList = files.map(function (file, i) {
- return { name: file, stat: stats[i] };
- });
-
- // sort file list
- fileList.sort(fileSort);
+ // create locals for rendering
+ var locals = {
+ directory: directory.name,
+ displayIcons: Boolean(icons),
+ fileList: files,
+ path: path,
+ style: style,
+ viewName: view
+ };
- // read stylesheet
- fs.readFile(stylesheet, 'utf8', function (err, style) {
+ // render html
+ render(locals, function (err, body) {
if (err) return next(err);
-
- // create locals for rendering
- var locals = {
- directory: dir,
- displayIcons: Boolean(icons),
- fileList: fileList,
- path: path,
- style: style,
- viewName: view
- };
-
- // render html
- render(locals, function (err, body) {
- if (err) return next(err);
- send(res, 'text/html', body)
- });
+ send(res, 'text/html', body)
});
});
};
@@ -218,36 +254,34 @@ serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path
* Respond with application/json.
*/
-serveIndex.json = function _json(req, res, files) {
+serveIndex.json = function _json(req, res, directory, nodes) {
+ var files = nodes.map(function (file) { return file.name })
send(res, 'application/json', JSON.stringify(files))
-};
+}
/**
* Respond with text/plain.
*/
-serveIndex.plain = function _plain(req, res, files) {
+serveIndex.plain = function _plain(req, res, directory, nodes) {
+ var files = nodes.map(function (file) { return file.name })
send(res, 'text/plain', (files.join('\n') + '\n'))
-};
+}
/**
* Map html `files`, returning an html unordered list.
* @private
*/
-function createHtmlFileList(files, dir, useIcons, view) {
- var html = ''
- + (view === 'details' ? (
- '') : '');
+function createHtmlFileList(files, dirname, useIcons, view) {
+ var html = templates.html.list
+ .replace(/{view}/g, view)
+ .replace(/{header}/g, view === 'details' ? templates.html.header : '')
- html += files.map(function (file) {
+ var items = files.map(function (file) {
var classes = [];
- var isDir = file.stat && file.stat.isDirectory();
- var path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
+ var isDir = 'inode/directory' === file.type
+ var path = dirname.split('/').map(function (c) { return encodeURIComponent(c); });
if (useIcons) {
classes.push('icon');
@@ -269,26 +303,22 @@ function createHtmlFileList(files, dir, useIcons, view) {
path.push(encodeURIComponent(file.name));
- var date = file.stat && file.name !== '..'
- ? file.stat.mtime.toLocaleDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
+ var date = file.lastModified && file.name !== '..'
+ ? file.lastModified.toLocaleDateString() + ' ' + file.lastModified.toLocaleTimeString()
: '';
- var size = file.stat && !isDir
- ? file.stat.size
+ var size = file.size && !isDir
+ ? file.size
: '';
- return '- '
- + '' + escapeHtml(file.name) + ''
- + '' + escapeHtml(size) + ''
- + '' + escapeHtml(date) + ''
- + '
';
+ return templates.html.item
+ .replace(/{path}/g, escapeHtml(normalizeSlashes(normalize(path.join('/')))))
+ .replace(/{classes}/g, escapeHtml(classes.join(' ')))
+ .replace(/{file\.name}/g, escapeHtml(file.name))
+ .replace(/{file\.size}/g, escapeHtml(size))
+ .replace(/{file\.lastModified/g, escapeHtml(date))
}).join('\n');
- html += '
';
-
- return html;
+ return html.replace(/{items}/g, items)
}
/**
@@ -302,10 +332,10 @@ function createHtmlRender(template) {
if (err) return callback(err);
var body = str
- .replace(/\{style\}/g, locals.style.concat(iconStyle(locals.fileList, locals.displayIcons)))
- .replace(/\{files\}/g, createHtmlFileList(locals.fileList, locals.directory, locals.displayIcons, locals.viewName))
- .replace(/\{directory\}/g, escapeHtml(locals.directory))
- .replace(/\{linked-path\}/g, htmlPath(locals.directory));
+ .replace(/{style}/g, locals.style.concat(iconStyle(locals.fileList, locals.displayIcons)))
+ .replace(/{files}/g, createHtmlFileList(locals.fileList, locals.directory, locals.displayIcons, locals.viewName))
+ .replace(/{directory}/g, escapeHtml(locals.directory))
+ .replace(/{linked-path}/g, htmlPath(locals.directory))
callback(null, body);
});
@@ -421,7 +451,7 @@ function iconStyle(files, useIcons) {
for (i = 0; i < files.length; i++) {
var file = files[i];
- var isDir = file.stat && file.stat.isDirectory();
+ var isDir = 'inode/directory' === file.type
var icon = isDir
? { className: 'icon-directory', fileName: icons.folder }
: iconLookup(file.name);
@@ -511,7 +541,7 @@ function send (res, type, body) {
* in same order.
*/
-function stat(dir, files, cb) {
+function fstat(dir, files, cb) {
var batch = new Batch();
batch.concurrency(10);
diff --git a/test/test.js b/test/test.js
index b0c9531d..3b7a0727 100644
--- a/test/test.js
+++ b/test/test.js
@@ -436,7 +436,9 @@ describe('serveIndex(root)', function () {
it('should provide "fileList" local', function (done) {
var server = createServer(fixtures, {'template': function (locals, callback) {
callback(null, JSON.stringify(locals.fileList.map(function (file) {
- file.stat = file.stat instanceof fs.Stats;
+ file.lastModified = file.lastModified instanceof Date
+ file.size = file.size >= 0
+ file.type = /\//.test(file.type)
return file;
})));
}});
@@ -444,7 +446,7 @@ describe('serveIndex(root)', function () {
request(server)
.get('/users/')
.set('Accept', 'text/html')
- .expect('[{"name":"..","stat":true},{"name":"#dir","stat":true},{"name":"index.html","stat":true},{"name":"tobi.txt","stat":true}]')
+ .expect('[{"name":"..","type":true,"size":true,"lastModified":true},{"name":"#dir","type":true,"size":true,"lastModified":true},{"name":"index.html","type":true,"size":true,"lastModified":true},{"name":"tobi.txt","type":true,"size":true,"lastModified":true}]')
.expect(200, done);
});
@@ -504,9 +506,9 @@ describe('serveIndex(root)', function () {
it('should get file list', function (done) {
var server = createServer()
- serveIndex.html = function (req, res, files) {
+ serveIndex.html = function (req, res, directory, files) {
var text = files
- .filter(function (f) { return /\.txt$/.test(f) })
+ .filter(function (f) { return /\.txt$/.test(f.name) })
.sort()
res.setHeader('Content-Type', 'text/html')
res.end('' + text.length + ' text files')
@@ -521,9 +523,9 @@ describe('serveIndex(root)', function () {
it('should get dir name', function (done) {
var server = createServer()
- serveIndex.html = function (req, res, files, next, dir) {
+ serveIndex.html = function (req, res, directory, files, next) {
res.setHeader('Content-Type', 'text/html')
- res.end('' + dir + '')
+ res.end('' + directory.name + '')
}
request(server)
@@ -535,7 +537,7 @@ describe('serveIndex(root)', function () {
it('should get template path', function (done) {
var server = createServer()
- serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
+ serveIndex.html = function (req, res, directory, files, next, showUp, icons, path, view, template) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(template)))
}
@@ -549,7 +551,7 @@ describe('serveIndex(root)', function () {
it('should get template with tokens', function (done) {
var server = createServer()
- serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
+ serveIndex.html = function (req, res, directory, files, next, showUp, icons, path, view, template) {
res.setHeader('Content-Type', 'text/html')
res.end(fs.readFileSync(template, 'utf8'))
}
@@ -567,7 +569,7 @@ describe('serveIndex(root)', function () {
it('should get stylesheet path', function (done) {
var server = createServer()
- serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
+ serveIndex.html = function (req, res, directory, files, next, showUp, icons, path, view, template, stylesheet) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(stylesheet)))
}
@@ -585,7 +587,7 @@ describe('serveIndex(root)', function () {
it('should get called with Accept: text/plain', function (done) {
var server = createServer()
- serveIndex.plain = function (req, res, files) {
+ serveIndex.plain = function (req, res, directory, files) {
res.setHeader('Content-Type', 'text/plain');
res.end('called');
}
@@ -603,7 +605,7 @@ describe('serveIndex(root)', function () {
it('should get called with Accept: application/json', function (done) {
var server = createServer()
- serveIndex.json = function (req, res, files) {
+ serveIndex.json = function (req, res, directory, files) {
res.setHeader('Content-Type', 'application/json');
res.end('"called"');
}