@@ -1,6 +1,7 @@
|
||||
unreleased
|
||||
==========
|
||||
|
||||
* Accept `function` value for `template` option
|
||||
* Send non-chunked response for `OPTIONS`
|
||||
* Stat parent directory when necessary
|
||||
* Use `Date.prototype.toLocaleDateString` to format date
|
||||
|
||||
@@ -56,15 +56,31 @@ Optional path to a CSS stylesheet. Defaults to a built-in stylesheet.
|
||||
|
||||
##### template
|
||||
|
||||
Optional path to an HTML template. Defaults to a built-in template.
|
||||
Optional path to an HTML template or a function that will render a HTML
|
||||
string. Defaults to a built-in template.
|
||||
|
||||
The following tokens are replaced in templates:
|
||||
When given a string, the string is used as a file path to load and then the
|
||||
following tokens are replaced in templates:
|
||||
|
||||
* `{directory}` with the name of the directory.
|
||||
* `{files}` with the HTML of an unordered list of file links.
|
||||
* `{linked-path}` with the HTML of a link to the directory.
|
||||
* `{style}` with the specified stylesheet and embedded images.
|
||||
|
||||
When given as a function, the function is called as `template(locals, callback)`
|
||||
and it needs to invoke `callback(error, htmlString)`. The following are the
|
||||
provided locals:
|
||||
|
||||
* `directory` is the directory being displayed (where `/` is the root).
|
||||
* `displayIcons` is a Boolean for if icons should be rendered or not.
|
||||
* `fileList` is a sorted array of files in the directory. The array contains
|
||||
objects with the following properties:
|
||||
- `name` is the relative name for the file.
|
||||
- `stat` is a `fs.Stats` object for the file.
|
||||
* `path` is the full filesystem path to `directory`.
|
||||
* `style` is the default stylesheet or the contents of the `stylesheet` option.
|
||||
* `viewName` is the view name provided by the `view` option.
|
||||
|
||||
##### view
|
||||
|
||||
Display mode. `tiles` and `details` are available. Defaults to `tiles`.
|
||||
|
||||
@@ -171,27 +171,44 @@ function serveIndex(root, options) {
|
||||
*/
|
||||
|
||||
serveIndex.html = function _html(req, res, files, next, dir, 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) {
|
||||
if (err) return next(err);
|
||||
fs.readFile(template, 'utf8', function(err, str){
|
||||
|
||||
// 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);
|
||||
|
||||
// read stylesheet
|
||||
fs.readFile(stylesheet, 'utf8', function (err, style) {
|
||||
if (err) return next(err);
|
||||
fs.readFile(stylesheet, 'utf8', function(err, style){
|
||||
|
||||
// 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);
|
||||
|
||||
var fileData = files.map(function (file, i) {
|
||||
return { name: file, stat: stats[i] };
|
||||
}).sort(fileSort);
|
||||
|
||||
var body = str
|
||||
.replace(/\{style\}/g, style.concat(iconStyle(fileData, icons)))
|
||||
.replace(/\{files\}/g, createHtmlFileList(fileData, dir, icons, view))
|
||||
.replace(/\{directory\}/g, escapeHtml(dir))
|
||||
.replace(/\{linked-path\}/g, htmlPath(dir));
|
||||
|
||||
var buf = new Buffer(body, 'utf8');
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.setHeader('Content-Length', buf.length);
|
||||
@@ -288,6 +305,27 @@ function createHtmlFileList(files, dir, useIcons, view) {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create function to render html.
|
||||
*/
|
||||
|
||||
function createHtmlRender(template) {
|
||||
return function render(locals, callback) {
|
||||
// read template
|
||||
fs.readFile(template, 'utf8', function (err, str) {
|
||||
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));
|
||||
|
||||
callback(null, body);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort function for with directories first.
|
||||
*/
|
||||
@@ -385,7 +423,7 @@ function iconLookup(filename) {
|
||||
* Load icon images, return css string.
|
||||
*/
|
||||
|
||||
function iconStyle (files, useIcons) {
|
||||
function iconStyle(files, useIcons) {
|
||||
if (!useIcons) return '';
|
||||
var className;
|
||||
var i;
|
||||
|
||||
+140
-44
@@ -304,6 +304,146 @@ describe('serveIndex(root)', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with "template" option', function () {
|
||||
describe('when setting a custom template file', function () {
|
||||
var server;
|
||||
before(function () {
|
||||
server = createServer(fixtures, {'template': __dirname + '/shared/template.html'});
|
||||
});
|
||||
|
||||
it('should respond with file list', function (done) {
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(/<a href="\/g%23%20%253%20o%20%26%20%252525%20%2537%20dir"/)
|
||||
.expect(/<a href="\/users"/)
|
||||
.expect(/<a href="\/file%20%231.txt"/)
|
||||
.expect(/<a href="\/todo.txt"/)
|
||||
.expect(200, done)
|
||||
});
|
||||
|
||||
it('should respond with testing template sentence', function (done) {
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, /This is the test template/, done)
|
||||
});
|
||||
|
||||
it('should have default styles', function (done) {
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, /ul#files/, done)
|
||||
});
|
||||
|
||||
it('should list directory twice', function (done) {
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(function (res) {
|
||||
var occurances = res.text.match(/directory \/users\//g)
|
||||
if (occurances && occurances.length === 2) return
|
||||
throw new Error('directory not listed twice')
|
||||
})
|
||||
.expect(200, done)
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setting a custom template function', function () {
|
||||
it('should invoke function to render', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(null, 'This is a template.');
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, 'This is a template.', done);
|
||||
});
|
||||
|
||||
it('should handle render errors', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(new Error('boom!'));
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(500, 'boom!', done);
|
||||
});
|
||||
|
||||
it('should provide "directory" local', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(null, JSON.stringify(locals.directory));
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, '"/users/"', done);
|
||||
});
|
||||
|
||||
it('should provide "displayIcons" local', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(null, JSON.stringify(locals.displayIcons));
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, 'false', done);
|
||||
});
|
||||
|
||||
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;
|
||||
return file;
|
||||
})));
|
||||
}});
|
||||
|
||||
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(200, done);
|
||||
});
|
||||
|
||||
it('should provide "path" local', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(null, JSON.stringify(locals.path));
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, JSON.stringify(path.join(fixtures, 'users/')), done);
|
||||
});
|
||||
|
||||
it('should provide "style" local', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(null, JSON.stringify(locals.style));
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, /#files \.icon \.name/, done);
|
||||
});
|
||||
|
||||
it('should provide "viewName" local', function (done) {
|
||||
var server = createServer(fixtures, {'template': function (locals, callback) {
|
||||
callback(null, JSON.stringify(locals.viewName));
|
||||
}});
|
||||
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, '"tiles"', done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using custom handler', function () {
|
||||
describe('exports.html', function () {
|
||||
alterProperty(serveIndex, 'html', serveIndex.html)
|
||||
@@ -520,50 +660,6 @@ describe('serveIndex(root)', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setting a custom template', function () {
|
||||
var server;
|
||||
before(function () {
|
||||
server = createServer(fixtures, {'template': __dirname + '/shared/template.html'});
|
||||
});
|
||||
|
||||
it('should respond with file list', function (done) {
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(/<a href="\/g%23%20%253%20o%20%26%20%252525%20%2537%20dir"/)
|
||||
.expect(/<a href="\/users"/)
|
||||
.expect(/<a href="\/file%20%231.txt"/)
|
||||
.expect(/<a href="\/todo.txt"/)
|
||||
.expect(200, done)
|
||||
});
|
||||
|
||||
it('should respond with testing template sentence', function (done) {
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, /This is the test template/, done)
|
||||
});
|
||||
|
||||
it('should have default styles', function (done) {
|
||||
request(server)
|
||||
.get('/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, /ul#files/, done)
|
||||
});
|
||||
|
||||
it('should list directory twice', function (done) {
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(function (res) {
|
||||
var occurances = res.text.match(/directory \/users\//g)
|
||||
if (occurances && occurances.length === 2) return
|
||||
throw new Error('directory not listed twice')
|
||||
})
|
||||
.expect(200, done)
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setting a custom stylesheet', function () {
|
||||
var server;
|
||||
before(function () {
|
||||
|
||||
Reference in New Issue
Block a user