Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show details plain #82

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 107 additions & 47 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

var accepts = require('accepts');
var bytes = require('bytes');
var createError = require('http-errors');
var debug = require('debug')('serve-index');
var escapeHtml = require('escape-html');
Expand Down Expand Up @@ -154,13 +155,64 @@ 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, {
// whether '..' should be shown
hasParent: showUp,
// whether to show icons
icons: icons,
// actual fs path
path: path,
// tiles or details
view: view,
// path to template
template: template,
// path to stylesheet
stylesheet: stylesheet
})
});
});
});
};
Expand All @@ -170,46 +222,35 @@ 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, options) {
var showUp = options.hasParant
var icons = options.icons
var path = options.path
var view = options.view
var template = options.template
var stylesheet = options.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)
});
});
};
Expand All @@ -218,24 +259,43 @@ 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) {
send(res, 'text/plain', (files.join('\n') + '\n'))
};
serveIndex.plain = function _plain(req, res, directory, nodes, next, options) {

function detailsView() {
return 'Directory listing for ' + directory.name.replace(/\/?$/, '/') + '\n\n' +
nodes.map(function (file) {

// human readable
var size = bytes.format(file.size, { decimalPlaces: 0 })

// organized by fixed-width for readability
return [ file.lastModified.toISOString(), size, file.name + ('inode/directory' === file.type ? '/' : '') ].join('\t')

}).join('\n') + '\n'
}

function tileView() {
return nodes.map(function (file) { return file.name }).join('\n') + '\n'
}

send(res, 'text/plain', ('details' === options.view) ? detailsView() : tileView())
}

/**
* Map html `files`, returning an html unordered list.
* @private
*/

function createHtmlFileList(files, dir, useIcons, view) {
function createHtmlFileList(files, dirname, useIcons, view) {
var html = '<ul id="files" class="view-' + escapeHtml(view) + '">'
+ (view === 'details' ? (
'<li class="header">'
Expand All @@ -246,8 +306,8 @@ function createHtmlFileList(files, dir, useIcons, view) {

html += 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');
Expand All @@ -269,11 +329,11 @@ 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 '<li><a href="'
Expand Down Expand Up @@ -421,7 +481,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);
Expand Down Expand Up @@ -511,7 +571,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);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dependencies": {
"accepts": "~1.3.4",
"batch": "0.6.1",
"bytes": "^3.0.0",
"debug": "2.6.9",
"escape-html": "~1.0.3",
"http-errors": "~1.6.2",
Expand Down
30 changes: 16 additions & 14 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,17 @@ 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;
})));
}});

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);
});

Expand Down Expand Up @@ -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('<b>' + text.length + ' text files</b>')
Expand All @@ -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('<b>' + dir + '</b>')
res.end('<b>' + directory.name + '</b>')
}

request(server)
Expand All @@ -535,9 +537,9 @@ 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, options) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(template)))
res.end(String(fs.existsSync(options.template)))
}

request(server)
Expand All @@ -549,9 +551,9 @@ 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, options) {
res.setHeader('Content-Type', 'text/html')
res.end(fs.readFileSync(template, 'utf8'))
res.end(fs.readFileSync(options.template, 'utf8'))
}

request(server)
Expand All @@ -567,9 +569,9 @@ 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, options) {
res.setHeader('Content-Type', 'text/html')
res.end(String(fs.existsSync(stylesheet)))
res.end(String(fs.existsSync(options.stylesheet)))
}

request(server)
Expand All @@ -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');
}
Expand All @@ -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"');
}
Expand Down