initial commit
|
@ -0,0 +1,41 @@
|
|||
Copyright 2018 AJ ONeal
|
||||
|
||||
This is open source software; you can redistribute it and/or modify it under the
|
||||
terms of either:
|
||||
|
||||
a) the "MIT License"
|
||||
b) the "Apache-2.0 License"
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Apache-2.0 License Summary
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,46 @@
|
|||
serve-tpl-download
|
||||
==================
|
||||
|
||||
A fork of the original `serve-index` template that, in combination with `serve-static`,
|
||||
provides support for direct file downloads.
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var serveIndex = require('serve-index');
|
||||
var serveTpl = require('serve-tpl-download');
|
||||
var serveDirs = serveIndex({ template: serveTpl() });
|
||||
|
||||
app.use('/', function (req, res, next) {
|
||||
|
||||
// enable direct downloads for express.static()
|
||||
if (req.query.download) {
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="'+path.basename(req.url)+'"');
|
||||
}
|
||||
express.static('./public')(req, res, function () {
|
||||
serveDirs(req, res, next);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Additional Options
|
||||
==================
|
||||
|
||||
### privatefiles
|
||||
|
||||
As an additional security precaution you can ignore files which are not world-readable.
|
||||
|
||||
For example, this would prevent files in a `~/.ssh` from being read even when `dotfiles` are allowed.
|
||||
|
||||
`{ privatefiles: 'ignore' }`
|
||||
|
||||
```js
|
||||
var serveTpl = require('serve-tpl-download');
|
||||
|
||||
var serveTemplate = serveTpl({ privatefiles: 'ignore' })
|
||||
```
|
||||
|
||||
This is most effective on Unix-based systems (macOS, Linux, Android).
|
||||
Windows may rely on ACLs instead of user-group-other style permissions.
|
|
@ -0,0 +1,456 @@
|
|||
/*!
|
||||
* telebit/serve-index
|
||||
* Copyright(c) 2018 AJ ONeal
|
||||
*
|
||||
* Derivative work of github.com/expressjs/serve-index
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var escapeHtml = require('escape-html');
|
||||
var fs = require('fs')
|
||||
, path = require('path')
|
||||
, normalize = path.normalize
|
||||
, sep = path.sep
|
||||
, extname = path.extname
|
||||
, join = path.join;
|
||||
var Batch = require('batch');
|
||||
var mime = require('mime-types');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
* @public
|
||||
*/
|
||||
|
||||
var defaultPagepath = path.join(__dirname, 'public/directory.html');
|
||||
var defaultStylepath = path.join(__dirname, 'public/style.css');
|
||||
var defaultList = '<ul id="files" class="view-{view}">{head}{files}</ul>';
|
||||
var defaultHead = '<li class="header">'
|
||||
+ '<span class="name">Name</span>'
|
||||
+ '<span class="size">Size</span>'
|
||||
+ '<span class="date">Modified</span>'
|
||||
+ '</li>'
|
||||
;
|
||||
var defaultFile = '<li><div>'
|
||||
+ '<a href="{path}?download=true" class="download" title="Download {file.name}">'
|
||||
+ '<span class="download">⬇️</span>'
|
||||
+ '</a>'
|
||||
+ '<a href="{path}" class="{classes}" title="{file.name}">'
|
||||
+ '<span class="name">{file.name}</span>'
|
||||
+ '<span class="size">{file.size}</span>'
|
||||
+ '<span class="date">{file.date}</span>'
|
||||
+ '</a>'
|
||||
+ '</div></li>'
|
||||
;
|
||||
module.exports = function (opts) {
|
||||
if (!opts) { opts = {}; }
|
||||
return createHtmlRender({
|
||||
pagepath: opts.pagepath || defaultPagepath
|
||||
, stylepath: opts.stylepath || defaultStylepath
|
||||
, list: opts.list || defaultList
|
||||
, head: opts.head || defaultHead
|
||||
, file: opts.file || defaultFile
|
||||
, privatefiles: opts.privatefiles
|
||||
});
|
||||
};
|
||||
|
||||
/*!
|
||||
* Icon cache.
|
||||
*/
|
||||
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Map html `files`, returning an html unordered list.
|
||||
* @private
|
||||
*/
|
||||
|
||||
function createHtmlFileList(opts, files, dir, useIcons, view) {
|
||||
var ftpls = files.map(function (file) {
|
||||
var classes = [];
|
||||
var isDir = file.stat && file.stat.isDirectory();
|
||||
var path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
|
||||
|
||||
if (useIcons) {
|
||||
classes.push('icon');
|
||||
|
||||
if (isDir) {
|
||||
classes.push('icon-directory');
|
||||
} else {
|
||||
var ext = extname(file.name);
|
||||
var icon = iconLookup(file.name);
|
||||
|
||||
classes.push('icon');
|
||||
classes.push('icon-' + ext.substring(1));
|
||||
|
||||
if (classes.indexOf(icon.className) === -1) {
|
||||
classes.push(icon.className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path.push(encodeURIComponent(file.name));
|
||||
|
||||
var date = file.stat && file.name !== '..'
|
||||
? file.stat.mtime.toLocaleDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
|
||||
: '';
|
||||
var size = file.stat && !isDir
|
||||
? file.stat.size
|
||||
: '';
|
||||
var OCTAL = 8;
|
||||
var WORLD_READ = parseInt(4, OCTAL); // R(4)W(2)X(1)
|
||||
var hasWorldRead = file.mode | WORLD_READ;
|
||||
|
||||
if (!hasWorldRead && 'ignore' === opts.privatefiles) {
|
||||
return '';
|
||||
}
|
||||
return opts.file.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.date}/g, escapeHtml(date))
|
||||
;
|
||||
}).filter(Boolean).join('\n');
|
||||
|
||||
var html = opts.list
|
||||
.replace(/{view}/g, escapeHtml(view))
|
||||
.replace(/{head}/g, view === 'details' ? opts.head : '')
|
||||
.replace(/{files}/g, ftpls)
|
||||
;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create function to render html.
|
||||
*/
|
||||
|
||||
function createHtmlRender(opts) {
|
||||
return function render(locals, callback) {
|
||||
// read template
|
||||
fs.readFile(opts.pagepath, 'utf8', function (err, pageStr) {
|
||||
fs.readFile(opts.stylepath, 'utf8', function (err, styleStr) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var body = pageStr
|
||||
.replace(/\{style\}/g, styleStr.concat(iconStyle(locals.fileList, locals.displayIcons)))
|
||||
.replace(/\{files\}/g, createHtmlFileList(opts, locals.fileList, locals.directory, locals.displayIcons, locals.viewName))
|
||||
.replace(/\{directory\}/g, escapeHtml(locals.directory))
|
||||
.replace(/\{linked-path\}/g, htmlPath(locals.directory))
|
||||
;
|
||||
|
||||
callback(null, body);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map html `dir`, returning a linked path.
|
||||
*/
|
||||
|
||||
function htmlPath(dir) {
|
||||
var parts = dir.split('/');
|
||||
var crumb = new Array(parts.length);
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
if (part) {
|
||||
parts[i] = encodeURIComponent(part);
|
||||
crumb[i] = '<a href="' + escapeHtml(parts.slice(0, i + 1).join('/')) + '">' + escapeHtml(part) + '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
return crumb.join(' / ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the icon data for the file name.
|
||||
*/
|
||||
|
||||
function iconLookup(filename) {
|
||||
var ext = extname(filename);
|
||||
|
||||
// try by extension
|
||||
if (icons[ext]) {
|
||||
return {
|
||||
className: 'icon-' + ext.substring(1),
|
||||
fileName: icons[ext]
|
||||
};
|
||||
}
|
||||
|
||||
var mimetype = mime.lookup(ext);
|
||||
|
||||
// default if no mime type
|
||||
if (mimetype === false) {
|
||||
return {
|
||||
className: 'icon-default',
|
||||
fileName: icons.default
|
||||
};
|
||||
}
|
||||
|
||||
// try by mime type
|
||||
if (icons[mimetype]) {
|
||||
return {
|
||||
className: 'icon-' + mimetype.replace('/', '-'),
|
||||
fileName: icons[mimetype]
|
||||
};
|
||||
}
|
||||
|
||||
var suffix = mimetype.split('+')[1];
|
||||
|
||||
if (suffix && icons['+' + suffix]) {
|
||||
return {
|
||||
className: 'icon-' + suffix,
|
||||
fileName: icons['+' + suffix]
|
||||
};
|
||||
}
|
||||
|
||||
var type = mimetype.split('/')[0];
|
||||
|
||||
// try by type only
|
||||
if (icons[type]) {
|
||||
return {
|
||||
className: 'icon-' + type,
|
||||
fileName: icons[type]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
className: 'icon-default',
|
||||
fileName: icons.default
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load icon images, return css string.
|
||||
*/
|
||||
|
||||
function iconStyle(files, useIcons) {
|
||||
if (!useIcons) return '';
|
||||
var i;
|
||||
var list = [];
|
||||
var rules = {};
|
||||
var selector;
|
||||
var selectors = {};
|
||||
var style = '';
|
||||
|
||||
for (i = 0; i < files.length; i++) {
|
||||
var file = files[i];
|
||||
|
||||
var isDir = file.stat && file.stat.isDirectory();
|
||||
var icon = isDir
|
||||
? { className: 'icon-directory', fileName: icons.folder }
|
||||
: iconLookup(file.name);
|
||||
var iconName = icon.fileName;
|
||||
|
||||
selector = '#files .' + icon.className + ' .name';
|
||||
|
||||
if (!rules[iconName]) {
|
||||
rules[iconName] = 'background-image: url(data:image/png;base64,' + load(iconName) + ');'
|
||||
selectors[iconName] = [];
|
||||
list.push(iconName);
|
||||
}
|
||||
|
||||
if (selectors[iconName].indexOf(selector) === -1) {
|
||||
selectors[iconName].push(selector);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < list.length; i++) {
|
||||
iconName = list[i];
|
||||
style += selectors[iconName].join(',\n') + ' {\n ' + rules[iconName] + '\n}\n';
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and cache the given `icon`.
|
||||
*
|
||||
* @param {String} icon
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function load(icon) {
|
||||
if (cache[icon]) return cache[icon];
|
||||
return cache[icon] = fs.readFileSync(__dirname + '/public/icons/' + icon, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the path separator from system separator
|
||||
* to URL separator, aka `/`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function normalizeSlashes(path) {
|
||||
return path.split(sep).join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a response.
|
||||
* @private
|
||||
*/
|
||||
|
||||
function send (res, type, body) {
|
||||
// security header for content sniffing
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
// standard headers
|
||||
res.setHeader('Content-Type', type + '; charset=utf-8');
|
||||
res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'));
|
||||
|
||||
// body
|
||||
res.end(body, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stat all files and return array of stat
|
||||
* in same order.
|
||||
*/
|
||||
|
||||
function stat(dir, files, cb) {
|
||||
var batch = new Batch();
|
||||
|
||||
batch.concurrency(10);
|
||||
|
||||
files.forEach(function(file){
|
||||
batch.push(function(done){
|
||||
fs.stat(join(dir, file), function(err, stat){
|
||||
if (err && err.code !== 'ENOENT') return done(err);
|
||||
|
||||
// pass ENOENT as null stat, not error
|
||||
done(null, stat || null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
batch.end(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon map.
|
||||
*/
|
||||
|
||||
var icons = {
|
||||
// base icons
|
||||
'default': 'page_white.png',
|
||||
'folder': 'folder.png',
|
||||
|
||||
// generic mime type icons
|
||||
'font': 'font.png',
|
||||
'image': 'image.png',
|
||||
'text': 'page_white_text.png',
|
||||
'video': 'film.png',
|
||||
|
||||
// generic mime suffix icons
|
||||
'+json': 'page_white_code.png',
|
||||
'+xml': 'page_white_code.png',
|
||||
'+zip': 'box.png',
|
||||
|
||||
// specific mime type icons
|
||||
'application/javascript': 'page_white_code_red.png',
|
||||
'application/json': 'page_white_code.png',
|
||||
'application/msword': 'page_white_word.png',
|
||||
'application/pdf': 'page_white_acrobat.png',
|
||||
'application/postscript': 'page_white_vector.png',
|
||||
'application/rtf': 'page_white_word.png',
|
||||
'application/vnd.ms-excel': 'page_white_excel.png',
|
||||
'application/vnd.ms-powerpoint': 'page_white_powerpoint.png',
|
||||
'application/vnd.oasis.opendocument.presentation': 'page_white_powerpoint.png',
|
||||
'application/vnd.oasis.opendocument.spreadsheet': 'page_white_excel.png',
|
||||
'application/vnd.oasis.opendocument.text': 'page_white_word.png',
|
||||
'application/x-7z-compressed': 'box.png',
|
||||
'application/x-sh': 'application_xp_terminal.png',
|
||||
'application/x-msaccess': 'page_white_database.png',
|
||||
'application/x-shockwave-flash': 'page_white_flash.png',
|
||||
'application/x-sql': 'page_white_database.png',
|
||||
'application/x-tar': 'box.png',
|
||||
'application/x-xz': 'box.png',
|
||||
'application/xml': 'page_white_code.png',
|
||||
'application/zip': 'box.png',
|
||||
'image/svg+xml': 'page_white_vector.png',
|
||||
'text/css': 'page_white_code.png',
|
||||
'text/html': 'page_white_code.png',
|
||||
'text/less': 'page_white_code.png',
|
||||
|
||||
// other, extension-specific icons
|
||||
'.accdb': 'page_white_database.png',
|
||||
'.apk': 'box.png',
|
||||
'.app': 'application_xp.png',
|
||||
'.as': 'page_white_actionscript.png',
|
||||
'.asp': 'page_white_code.png',
|
||||
'.aspx': 'page_white_code.png',
|
||||
'.bat': 'application_xp_terminal.png',
|
||||
'.bz2': 'box.png',
|
||||
'.c': 'page_white_c.png',
|
||||
'.cab': 'box.png',
|
||||
'.cfm': 'page_white_coldfusion.png',
|
||||
'.clj': 'page_white_code.png',
|
||||
'.cc': 'page_white_cplusplus.png',
|
||||
'.cgi': 'application_xp_terminal.png',
|
||||
'.cpp': 'page_white_cplusplus.png',
|
||||
'.cs': 'page_white_csharp.png',
|
||||
'.db': 'page_white_database.png',
|
||||
'.dbf': 'page_white_database.png',
|
||||
'.deb': 'box.png',
|
||||
'.dll': 'page_white_gear.png',
|
||||
'.dmg': 'drive.png',
|
||||
'.docx': 'page_white_word.png',
|
||||
'.erb': 'page_white_ruby.png',
|
||||
'.exe': 'application_xp.png',
|
||||
'.fnt': 'font.png',
|
||||
'.gam': 'controller.png',
|
||||
'.gz': 'box.png',
|
||||
'.h': 'page_white_h.png',
|
||||
'.ini': 'page_white_gear.png',
|
||||
'.iso': 'cd.png',
|
||||
'.jar': 'box.png',
|
||||
'.java': 'page_white_cup.png',
|
||||
'.jsp': 'page_white_cup.png',
|
||||
'.lua': 'page_white_code.png',
|
||||
'.lz': 'box.png',
|
||||
'.lzma': 'box.png',
|
||||
'.m': 'page_white_code.png',
|
||||
'.map': 'map.png',
|
||||
'.msi': 'box.png',
|
||||
'.mv4': 'film.png',
|
||||
'.pdb': 'page_white_database.png',
|
||||
'.php': 'page_white_php.png',
|
||||
'.pl': 'page_white_code.png',
|
||||
'.pkg': 'box.png',
|
||||
'.pptx': 'page_white_powerpoint.png',
|
||||
'.psd': 'page_white_picture.png',
|
||||
'.py': 'page_white_code.png',
|
||||
'.rar': 'box.png',
|
||||
'.rb': 'page_white_ruby.png',
|
||||
'.rm': 'film.png',
|
||||
'.rom': 'controller.png',
|
||||
'.rpm': 'box.png',
|
||||
'.sass': 'page_white_code.png',
|
||||
'.sav': 'controller.png',
|
||||
'.scss': 'page_white_code.png',
|
||||
'.srt': 'page_white_text.png',
|
||||
'.tbz2': 'box.png',
|
||||
'.tgz': 'box.png',
|
||||
'.tlz': 'box.png',
|
||||
'.vb': 'page_white_code.png',
|
||||
'.vbs': 'page_white_code.png',
|
||||
'.xcf': 'page_white_picture.png',
|
||||
'.xlsx': 'page_white_excel.png',
|
||||
'.yaws': 'page_white_code.png'
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "serve-tpl-download",
|
||||
"version": "1.0.0",
|
||||
"description": "A template for serve-static with a direct download option (requires serve-index)",
|
||||
"homepage": "https://git.coolaj86.com/coolaj86/serve-tpl-download.js",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.coolaj86.com/coolaj86/serve-tpl-download.js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"serve-static",
|
||||
"serve-index"
|
||||
],
|
||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||
"license": "(MIT OR Apache-2.0)"
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>listing directory {directory}</title>
|
||||
<style>{style}</style>
|
||||
<script>
|
||||
function $(id){
|
||||
var el = 'string' == typeof id
|
||||
? document.getElementById(id)
|
||||
: id;
|
||||
|
||||
el.on = function(event, fn){
|
||||
if ('content loaded' == event) {
|
||||
event = window.attachEvent ? "load" : "DOMContentLoaded";
|
||||
}
|
||||
el.addEventListener
|
||||
? el.addEventListener(event, fn, false)
|
||||
: el.attachEvent("on" + event, fn);
|
||||
};
|
||||
|
||||
el.all = function(selector){
|
||||
return $(el.querySelectorAll(selector));
|
||||
};
|
||||
|
||||
el.each = function(fn){
|
||||
for (var i = 0, len = el.length; i < len; ++i) {
|
||||
fn($(el[i]), i);
|
||||
}
|
||||
};
|
||||
|
||||
el.getClasses = function(){
|
||||
return this.getAttribute('class').split(/\s+/);
|
||||
};
|
||||
|
||||
el.addClass = function(name){
|
||||
var classes = this.getAttribute('class');
|
||||
el.setAttribute('class', classes
|
||||
? classes + ' ' + name
|
||||
: name);
|
||||
};
|
||||
|
||||
el.removeClass = function(name){
|
||||
var classes = this.getClasses().filter(function(curr){
|
||||
return curr != name;
|
||||
});
|
||||
this.setAttribute('class', classes.join(' '));
|
||||
};
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
function search() {
|
||||
var str = $('search').value.toLowerCase();
|
||||
var links = $('files').all('a');
|
||||
|
||||
links.each(function(link){
|
||||
var text = link.textContent.toLowerCase();
|
||||
|
||||
if ('..' == text) return;
|
||||
if (str.length && ~text.indexOf(str)) {
|
||||
link.addClass('highlight');
|
||||
} else {
|
||||
link.removeClass('highlight');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on('content loaded', function(){
|
||||
$('search').on('keyup', search);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="directory">
|
||||
<input id="search" type="text" placeholder="Search" autocomplete="off" />
|
||||
<div id="wrapper">
|
||||
<h1><a href="/">~</a>{linked-path}</h1>
|
||||
{files}
|
||||
</div>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
window.fetch(document.location, {
|
||||
method: 'GET'
|
||||
, cache: 'no-store'
|
||||
, headers: {
|
||||
'Accept': 'application/json' //; charset=utf-8
|
||||
}
|
||||
}).then(function (resp) {
|
||||
return resp.json().then(function (files) {
|
||||
console.log(files);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 426 B |
After Width: | Height: | Size: 507 B |
After Width: | Height: | Size: 555 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 666 B |
After Width: | Height: | Size: 346 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 634 B |
After Width: | Height: | Size: 567 B |
After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 804 B |
After Width: | Height: | Size: 635 B |
After Width: | Height: | Size: 739 B |
After Width: | Height: | Size: 794 B |
After Width: | Height: | Size: 818 B |
After Width: | Height: | Size: 663 B |
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 807 B |
After Width: | Height: | Size: 793 B |
After Width: | Height: | Size: 817 B |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 833 B |
After Width: | Height: | Size: 779 B |
After Width: | Height: | Size: 621 B |
After Width: | Height: | Size: 801 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 830 B |
After Width: | Height: | Size: 813 B |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 641 B |
After Width: | Height: | Size: 858 B |
After Width: | Height: | Size: 774 B |
After Width: | Height: | Size: 294 B |
After Width: | Height: | Size: 591 B |
After Width: | Height: | Size: 664 B |
After Width: | Height: | Size: 512 B |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 666 B |
After Width: | Height: | Size: 603 B |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 592 B |
After Width: | Height: | Size: 724 B |
After Width: | Height: | Size: 309 B |
After Width: | Height: | Size: 621 B |
After Width: | Height: | Size: 700 B |
After Width: | Height: | Size: 639 B |
After Width: | Height: | Size: 579 B |
After Width: | Height: | Size: 536 B |
After Width: | Height: | Size: 638 B |
After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 623 B |
After Width: | Height: | Size: 663 B |
After Width: | Height: | Size: 676 B |
After Width: | Height: | Size: 582 B |
After Width: | Height: | Size: 639 B |
After Width: | Height: | Size: 402 B |
After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 612 B |
After Width: | Height: | Size: 603 B |
After Width: | Height: | Size: 296 B |
After Width: | Height: | Size: 616 B |
After Width: | Height: | Size: 669 B |
After Width: | Height: | Size: 614 B |
After Width: | Height: | Size: 554 B |
After Width: | Height: | Size: 706 B |
After Width: | Height: | Size: 779 B |
After Width: | Height: | Size: 688 B |
After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 538 B |
After Width: | Height: | Size: 650 B |
After Width: | Height: | Size: 588 B |
After Width: | Height: | Size: 523 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 317 B |
After Width: | Height: | Size: 565 B |
After Width: | Height: | Size: 634 B |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 315 B |
After Width: | Height: | Size: 668 B |
After Width: | Height: | Size: 644 B |
After Width: | Height: | Size: 702 B |
After Width: | Height: | Size: 309 B |
After Width: | Height: | Size: 651 B |
After Width: | Height: | Size: 734 B |
After Width: | Height: | Size: 613 B |
After Width: | Height: | Size: 386 B |
After Width: | Height: | Size: 777 B |
After Width: | Height: | Size: 903 B |
|
@ -0,0 +1,266 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 80px 100px;
|
||||
font: 13px "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9));
|
||||
background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
|
||||
background-repeat: no-repeat;
|
||||
color: #555;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-size: 22px;
|
||||
color: #343434;
|
||||
}
|
||||
h1 em, h2 em {
|
||||
padding: 0 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
h1 {
|
||||
font-size: 60px;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
h3 {
|
||||
margin: 5px 0 10px 0;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 18px;
|
||||
}
|
||||
ul li {
|
||||
list-style: none;
|
||||
}
|
||||
ul li:hover {
|
||||
cursor: pointer;
|
||||
color: #2e2e2e;
|
||||
}
|
||||
ul li .path {
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
ul li .line {
|
||||
padding-right: 5px;
|
||||
font-style: italic;
|
||||
}
|
||||
ul li:first-child .path {
|
||||
padding-left: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
a {
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #303030;
|
||||
}
|
||||
#stacktrace {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.directory h1 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
ul#files {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
ul#files li {
|
||||
float: left;
|
||||
width: 30%;
|
||||
line-height: 25px;
|
||||
margin: 1px;
|
||||
}
|
||||
ul#files li div {
|
||||
display: block;
|
||||
height: 25px;
|
||||
border: 1px solid transparent;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
ul#files li div:focus,
|
||||
ul#files li div:hover {
|
||||
background: rgba(255,255,255,0.65);
|
||||
border: 1px solid #ececec;
|
||||
}
|
||||
ul#files li div.highlight {
|
||||
-webkit-transition: background .4s ease-in-out;
|
||||
background: #ffff4f;
|
||||
border-color: #E9DC51;
|
||||
}
|
||||
#search {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 90px;
|
||||
-webkit-transition: width ease 0.2s, opacity ease 0.4s;
|
||||
-moz-transition: width ease 0.2s, opacity ease 0.4s;
|
||||
-webkit-border-radius: 32px;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
|
||||
-moz-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-align: left;
|
||||
font: 13px "Helvetica Neue", Arial, sans-serif;
|
||||
padding: 4px 10px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
margin-bottom: 0;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
color: #888;
|
||||
}
|
||||
#search:focus {
|
||||
width: 120px;
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
/*views*/
|
||||
#files span {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-indent: 10px;
|
||||
}
|
||||
#files .name {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#files .icon .name {
|
||||
text-indent: 28px;
|
||||
}
|
||||
|
||||
/*tiles*/
|
||||
.view-tiles .download {
|
||||
background-position: 8px 5px;
|
||||
}
|
||||
.view-tiles .name {
|
||||
width: 100%;
|
||||
background-position: 8px 5px;
|
||||
}
|
||||
#files span.size,
|
||||
#files span.date,
|
||||
.view-tiles .size,
|
||||
.view-tiles .date {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*details*/
|
||||
ul#files.view-details li {
|
||||
float: none;
|
||||
display: block;
|
||||
width: 90%;
|
||||
}
|
||||
ul#files.view-details li.header {
|
||||
height: 25px;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.view-details .header {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.view-details .name {
|
||||
width: 60%;
|
||||
background-position: 8px 5px;
|
||||
}
|
||||
.view-details .size {
|
||||
width: 10%;
|
||||
}
|
||||
.view-details .date {
|
||||
width: 30%;
|
||||
}
|
||||
.view-details .size,
|
||||
.view-details .date {
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
ul#files li a.download {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/*mobile*/
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
#search {
|
||||
position: static;
|
||||
width: 100%;
|
||||
font-size: 2em;
|
||||
line-height: 1.8em;
|
||||
text-indent: 10px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 10px 0;
|
||||
margin: 0;
|
||||
}
|
||||
#search:focus {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
.directory h1 {
|
||||
font-size: 2em;
|
||||
line-height: 1.5em;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
padding: 15px 10px;
|
||||
margin: 0;
|
||||
}
|
||||
ul#files {
|
||||
border-top: 1px solid #cacaca;
|
||||
}
|
||||
ul#files li {
|
||||
float: none;
|
||||
width: auto !important;
|
||||
display: block;
|
||||
border-bottom: 1px solid #cacaca;
|
||||
font-size: 2em;
|
||||
line-height: 1.2em;
|
||||
text-indent: 0;
|
||||
margin: 0;
|
||||
}
|
||||
ul#files li:nth-child(odd) {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
ul#files li div {
|
||||
height: auto;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
ul#files li a:focus,
|
||||
ul#files li a:hover {
|
||||
border: 0;
|
||||
}
|
||||
#files .header,
|
||||
#files .size,
|
||||
#files .date {
|
||||
display: none !important;
|
||||
}
|
||||
#files .name {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-indent: 0;
|
||||
background-position: 0 50%;
|
||||
}
|
||||
#files .icon .name {
|
||||
text-indent: 41px;
|
||||
}
|
||||
}
|