Server render auto-index pages
Also, load the autoIndex bundle on the client so we can use React instead of using an inline script.
This commit is contained in:
23
server/client/autoIndex/App.css
Normal file
23
server/client/autoIndex/App.css
Normal file
@ -0,0 +1,23 @@
|
||||
.app {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.app-version-selector {
|
||||
line-height: 2.25em;
|
||||
float: right;
|
||||
}
|
||||
.app-version-selector select {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.app-address {
|
||||
text-align: right;
|
||||
}
|
||||
77
server/client/autoIndex/App.js
Normal file
77
server/client/autoIndex/App.js
Normal file
@ -0,0 +1,77 @@
|
||||
require("./App.css");
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const DirectoryListing = require("./DirectoryListing");
|
||||
|
||||
class App extends React.Component {
|
||||
static defaultProps = {
|
||||
availableVersions: []
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
window.location.href = window.location.href.replace(
|
||||
"@" + this.props.packageVersion,
|
||||
"@" + event.target.value
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="app-header">
|
||||
<h1>
|
||||
Index of /{this.props.packageName}@{this.props.packageVersion}
|
||||
{this.props.filename}
|
||||
</h1>
|
||||
|
||||
<div className="app-version-selector">
|
||||
Version:{" "}
|
||||
<select
|
||||
id="version"
|
||||
defaultValue={this.props.packageVersion}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{this.props.availableVersions.map(v => (
|
||||
<option key={v} value={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<hr />
|
||||
|
||||
<DirectoryListing
|
||||
filename={this.props.filename}
|
||||
entry={this.props.entry}
|
||||
entries={this.props.entries}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<address className="app-address">
|
||||
{this.props.packageName}@{this.props.packageVersion}
|
||||
</address>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
const entryType = PropTypes.object;
|
||||
|
||||
App.propTypes = {
|
||||
packageName: PropTypes.string.isRequired,
|
||||
packageVersion: PropTypes.string.isRequired,
|
||||
availableVersions: PropTypes.arrayOf(PropTypes.string),
|
||||
filename: PropTypes.string.isRequired,
|
||||
entry: entryType.isRequired,
|
||||
entries: PropTypes.objectOf(entryType).isRequired
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = App;
|
||||
17
server/client/autoIndex/DirectoryListing.css
Normal file
17
server/client/autoIndex/DirectoryListing.css
Normal file
@ -0,0 +1,17 @@
|
||||
.directory-listing table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: 0.85em Monaco, monospace;
|
||||
}
|
||||
|
||||
.directory-listing tr.even {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.directory-listing th {
|
||||
text-align: left;
|
||||
}
|
||||
.directory-listing th,
|
||||
.directory-listing td {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
126
server/client/autoIndex/DirectoryListing.js
Normal file
126
server/client/autoIndex/DirectoryListing.js
Normal file
@ -0,0 +1,126 @@
|
||||
require("./DirectoryListing.css");
|
||||
|
||||
const React = require("react");
|
||||
const formatBytes = require("pretty-bytes");
|
||||
const sortBy = require("sort-by");
|
||||
|
||||
function getDirname(name) {
|
||||
return (
|
||||
name
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/") || "."
|
||||
);
|
||||
}
|
||||
|
||||
function getMatchingEntries(entry, entries) {
|
||||
const dirname = entry.name || ".";
|
||||
|
||||
return Object.keys(entries)
|
||||
.filter(name => entry.name !== name && getDirname(name) === dirname)
|
||||
.map(name => entries[name]);
|
||||
}
|
||||
|
||||
function getRelativeName(base, name) {
|
||||
return base.length ? name.substr(base.length + 1) : name;
|
||||
}
|
||||
|
||||
function DirectoryListing({ filename, entry, entries }) {
|
||||
const rows = [];
|
||||
|
||||
if (filename !== "/") {
|
||||
rows.push(
|
||||
<tr key="..">
|
||||
<td>
|
||||
<a title="Parent directory" href="../">
|
||||
..
|
||||
</a>
|
||||
</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
const matchingEntries = getMatchingEntries(entry, entries);
|
||||
|
||||
matchingEntries
|
||||
.filter(({ type }) => type === "directory")
|
||||
.sort(sortBy("name"))
|
||||
.forEach(({ name }) => {
|
||||
const relName = getRelativeName(entry.name, name);
|
||||
const href = relName + "/";
|
||||
|
||||
rows.push(
|
||||
<tr key={name}>
|
||||
<td>
|
||||
<a title={relName} href={href}>
|
||||
{href}
|
||||
</a>
|
||||
</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
matchingEntries
|
||||
.filter(({ type }) => type === "file")
|
||||
.sort(sortBy("name"))
|
||||
.forEach(({ name, size, contentType, lastModified }) => {
|
||||
const relName = getRelativeName(entry.name, name);
|
||||
|
||||
rows.push(
|
||||
<tr key={name}>
|
||||
<td>
|
||||
<a title={relName} href={relName}>
|
||||
{relName}
|
||||
</a>
|
||||
</td>
|
||||
<td>{contentType}</td>
|
||||
<td>{formatBytes(size)}</td>
|
||||
<td>{lastModified}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="directory-listing">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Size</th>
|
||||
<th>Last Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, index) =>
|
||||
React.cloneElement(row, {
|
||||
className: index % 2 ? "odd" : "even"
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
const entryType = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
DirectoryListing.propTypes = {
|
||||
filename: PropTypes.string.isRequired,
|
||||
entry: entryType.isRequired,
|
||||
entries: PropTypes.objectOf(entryType).isRequired
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = DirectoryListing;
|
||||
Reference in New Issue
Block a user