initial commit
This commit is contained in:
commit
445c5c35be
99
autoindex.css
Normal file
99
autoindex.css
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
html,body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: #0b0035;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #5799ff;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
table th, table td {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
table a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
table tr:nth-child(2n) {
|
||||||
|
background-color: #09002b;
|
||||||
|
}
|
||||||
|
.btn-back {
|
||||||
|
background-color: #001153 !important;
|
||||||
|
}
|
||||||
|
.btn-back a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.btn-back #back-arrow {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
width: 80vw;
|
||||||
|
margin: auto;
|
||||||
|
background-color: #070022;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.enhanced a {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.enhanced a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.enhanced a:hover .movie-title {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.enhanced a .thumbnail {
|
||||||
|
max-width: 180px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: max-content;
|
||||||
|
}
|
||||||
|
.meta-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.movie-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
.movie-description {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.plain-mode {
|
||||||
|
padding: 1rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.plain-mode label {
|
||||||
|
margin-left: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
.inner {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
207
autoindex.js
Normal file
207
autoindex.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
const allLinks = [...document.getElementsByTagName('a')];
|
||||||
|
const bodyInner = document.querySelector('.inner');
|
||||||
|
const tableElem = document.querySelector('table');
|
||||||
|
const infoRegex = /(-thumb)?\.(jpg|nfo|srt)$/;
|
||||||
|
|
||||||
|
const arrow = `
|
||||||
|
<svg version="1.1" id="back-arrow" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 492 492" style="enable-background:new 0 0 492 492;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path fill="#fff" d="M464.344,207.418l0.768,0.168H135.888l103.496-103.724c5.068-5.064,7.848-11.924,7.848-19.124
|
||||||
|
c0-7.2-2.78-14.012-7.848-19.088L223.28,49.538c-5.064-5.064-11.812-7.864-19.008-7.864c-7.2,0-13.952,2.78-19.016,7.844
|
||||||
|
L7.844,226.914C2.76,231.998-0.02,238.77,0,245.974c-0.02,7.244,2.76,14.02,7.844,19.096l177.412,177.412
|
||||||
|
c5.064,5.06,11.812,7.844,19.016,7.844c7.196,0,13.944-2.788,19.008-7.844l16.104-16.112c5.068-5.056,7.848-11.808,7.848-19.008
|
||||||
|
c0-7.196-2.78-13.592-7.848-18.652L134.72,284.406h329.992c14.828,0,27.288-12.78,27.288-27.6v-22.788
|
||||||
|
C492,219.198,479.172,207.418,464.344,207.418z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
|
||||||
|
function findByHref(href) {
|
||||||
|
return allLinks.filter((link) => decodeURIComponent(link.getAttribute('href')) === decodeURIComponent(href));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findByInfoFile(infoFile) {
|
||||||
|
const found = ['avi', 'mp4', 'mkv'].reduce((last, current) => {
|
||||||
|
const element = findByHref(infoFile.replace(infoRegex, '.' + current))[0];
|
||||||
|
return element ? element : last;
|
||||||
|
}, null);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deduplicateJointEpisode(textContent) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const lns = textContent.split('\n');
|
||||||
|
const result = [];
|
||||||
|
let passed = 0;
|
||||||
|
for (const line of lns) {
|
||||||
|
if (line.startsWith('<episodedetails>')) {
|
||||||
|
if (passed === 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
passed = 1;
|
||||||
|
}
|
||||||
|
result.push(line);
|
||||||
|
}
|
||||||
|
resolve(result.join('\n'));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new window.DOMParser();
|
||||||
|
async function createXMLRequest(nfo) {
|
||||||
|
return fetch(nfo)
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((text) => deduplicateJointEpisode(text))
|
||||||
|
.then((str) => parser.parseFromString(str, 'text/xml'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOrImproveMovieMeta(original, thumbnail, nfo) {
|
||||||
|
let imgTag = `<img class="thumbnail" src="${thumbnail}"/>`;
|
||||||
|
let movieTitle = `<div class="movie-title">${original.innerText}</div>`;
|
||||||
|
let movieDescription = `<div class="movie-description"></div>`;
|
||||||
|
|
||||||
|
if (!thumbnail && !nfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original.parentElement.classList.contains('enhanced')) {
|
||||||
|
imgTag = original.querySelector('.thumbnail');
|
||||||
|
movieTitle = original.querySelector('.movie-title');
|
||||||
|
movieDescription = original.querySelector('.movie-description');
|
||||||
|
if (thumbnail) {
|
||||||
|
imgTag.src = thumbnail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
original.classList.add('movie');
|
||||||
|
original.innerHTML = imgTag + '<div class="meta-wrap">' + movieTitle + movieDescription + '</div>';
|
||||||
|
original.parentElement.classList.add('enhanced');
|
||||||
|
imgTag = original.querySelector('.thumbnail');
|
||||||
|
movieTitle = original.querySelector('.movie-title');
|
||||||
|
movieDescription = original.querySelector('.movie-description');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nfo) {
|
||||||
|
createXMLRequest(nfo.href)
|
||||||
|
.then((content) => {
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleEl = content.querySelector('title');
|
||||||
|
const plotEl = content.querySelector('plot');
|
||||||
|
const airedEl = content.querySelector('aired');
|
||||||
|
|
||||||
|
if (!titleEl || !plotEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (airedEl) {
|
||||||
|
const timestamp = original.parentElement.parentElement.querySelector('.timestamp');
|
||||||
|
if (timestamp) {
|
||||||
|
timestamp.innerText = airedEl.textContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const episode = content.querySelector('episode');
|
||||||
|
const season = content.querySelector('season');
|
||||||
|
let title = titleEl.textContent
|
||||||
|
if (episode && season) {
|
||||||
|
title = `(S${season.textContent} E${episode.textContent}) ` + title;
|
||||||
|
}
|
||||||
|
|
||||||
|
movieTitle.innerText = title;
|
||||||
|
movieDescription.innerText = plotEl.textContent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tvShow;
|
||||||
|
|
||||||
|
function accountForMetadata() {
|
||||||
|
allLinks.forEach((link) => {
|
||||||
|
const url = link.getAttribute('href');
|
||||||
|
if (url.match(infoRegex)) {
|
||||||
|
link.parentElement.parentElement.style.display = 'none';
|
||||||
|
let findOriginal
|
||||||
|
if (url === 'tvshow.nfo' || url === 'poster.jpg') {
|
||||||
|
// dirty hack
|
||||||
|
if (url === 'poster.jpg' && window.location.pathname.includes('/movies/')) {
|
||||||
|
findOriginal = document.querySelector('a:not([href=".."])');
|
||||||
|
} else {
|
||||||
|
if (tvShow) {
|
||||||
|
findOriginal = tvShow;
|
||||||
|
} else {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'highlight';
|
||||||
|
tvShow = document.createElement('a');
|
||||||
|
tvShow.className = 'movie';
|
||||||
|
wrapper.appendChild(tvShow);
|
||||||
|
bodyInner.insertBefore(wrapper, tableElem);
|
||||||
|
findOriginal = tvShow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (url.startsWith('season') && url.includes('poster')) {
|
||||||
|
const sn = url.match(/season(\d+)/);
|
||||||
|
if (sn) {
|
||||||
|
const number = parseInt(sn[1], 10);
|
||||||
|
const findByNumber = allLinks.filter((url) => url.innerText.includes(number));
|
||||||
|
if (findByNumber.length) {
|
||||||
|
findOriginal = findByNumber[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
findOriginal = findByInfoFile(encodeURIComponent(decodeURIComponent(url)));
|
||||||
|
}
|
||||||
|
if (findOriginal) {
|
||||||
|
createOrImproveMovieMeta(findOriginal,
|
||||||
|
url.match(/.jpg$/) ? link : undefined,
|
||||||
|
url.match(/.nfo$/) ? link : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (url === '..') {
|
||||||
|
link.parentElement.parentElement.classList.add('btn-back');
|
||||||
|
link.innerHTML = arrow + 'Back (..)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToggleCheckbox(plainMode) {
|
||||||
|
const form = document.createElement('div');
|
||||||
|
const checkbox = document.createElement('input');
|
||||||
|
const label = document.createElement('label');
|
||||||
|
form.className = 'plain-mode';
|
||||||
|
checkbox.type = 'checkbox';
|
||||||
|
checkbox.id = 'plainmode'
|
||||||
|
label.innerText = 'Disable metadata / display plain index listing (less bandwidth required)';
|
||||||
|
label.setAttribute('for', 'plainmode');
|
||||||
|
form.appendChild(checkbox);
|
||||||
|
form.appendChild(label)
|
||||||
|
bodyInner.appendChild(form);
|
||||||
|
|
||||||
|
checkbox.addEventListener('change', (e) => {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
window.localStorage.setItem('plainMode', 'true');
|
||||||
|
} else {
|
||||||
|
window.localStorage.removeItem('plainMode');
|
||||||
|
}
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
checkbox.checked = plainMode === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('localStorage' in window) {
|
||||||
|
const plainMode = window.localStorage.getItem('plainMode');
|
||||||
|
if (plainMode !== 'true') {
|
||||||
|
accountForMetadata();
|
||||||
|
}
|
||||||
|
createToggleCheckbox(plainMode);
|
||||||
|
} else {
|
||||||
|
accountForMetadata();
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
const loc = 'Index of ' + decodeURIComponent(document.location.pathname);
|
||||||
|
document.getElementById('dirname').innerHTML = loc;
|
||||||
|
document.title = loc;
|
||||||
|
*/
|
74
autoindex.xslt
Normal file
74
autoindex.xslt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/autoindex.css"/>
|
||||||
|
<script src="/static/autoindex.js" defer="defer"></script>
|
||||||
|
<title>Index of <xsl:value-of select="$path"/></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="inner">
|
||||||
|
<h1 id="dirname">Index of <xsl:value-of select="$path"/></h1>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="..">..</a></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<xsl:for-each select="list/file | list/directory">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="">
|
||||||
|
<xsl:attribute name="href">
|
||||||
|
<xsl:value-of select="."/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="@size">
|
||||||
|
<xsl:value-of select="."/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select="."/>/
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="@size > 1099511627776">
|
||||||
|
<xsl:value-of select="round(@size * 10 div 1099511627776) div 10"/> TiB
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="@size > 1073741824">
|
||||||
|
<xsl:value-of select="round(@size * 10 div 1073741824) div 10"/> GiB
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="@size > 1048576">
|
||||||
|
<xsl:value-of select="round(@size * 10 div 1048576) div 10"/> MiB
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="@size > 1024">
|
||||||
|
<xsl:value-of select="round(@size * 10 div 1024) div 10"/> KiB
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="@size">
|
||||||
|
<xsl:value-of select="@size"/> B
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
-
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp">
|
||||||
|
<xsl:value-of select="@mtime"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</xsl:for-each>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html></xsl:template>
|
||||||
|
</xsl:stylesheet>
|
8
nginx.example.conf
Normal file
8
nginx.example.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
location / {
|
||||||
|
autoindex on;
|
||||||
|
autoindex_exact_size off;
|
||||||
|
autoindex_localtime on;
|
||||||
|
autoindex_format xml;
|
||||||
|
xslt_string_param path $uri;
|
||||||
|
xslt_stylesheet autoindex.xslt;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user