From 24badd5cb870f14df5650803e1ec37f467720ce0 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 30 Jul 2023 08:42:38 +0300 Subject: [PATCH] breadcrumbs --- autoindex.css | 35 +++++++++++ autoindex.js | 165 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 153 insertions(+), 47 deletions(-) diff --git a/autoindex.css b/autoindex.css index a761c89..5c0a69a 100644 --- a/autoindex.css +++ b/autoindex.css @@ -132,6 +132,41 @@ button .tooltip { .timestamp .original { color: gray; } +#dirname { + padding: 1rem; +} +.directory#dirname { + padding: 0; + display: flex; + flex-wrap: wrap; + margin-bottom: 0.5rem; +} +.directory-link { + padding: 0.5rem 1rem 0.5rem 3rem; + background-color: #05275b; + display: flex; + position: relative; + text-decoration: none; + white-space: nowrap; +} +.directory-link:nth-child(2n+1) { + background-color: #0b3a80; +} +.directory-link::after { + content: ''; + width: 0; + height: 0; + border-top: 29px solid transparent; + border-bottom: 29px solid transparent; + border-left: 29px solid #05275b; + position: absolute; + right: -29px; + top: 0; + z-index: 10; +} +.directory-link:nth-child(2n+1)::after { + border-left-color: #0b3a80; +} @media screen and (max-width: 1200px) { .inner { width: 100%; diff --git a/autoindex.js b/autoindex.js index 563898c..5a5f18f 100644 --- a/autoindex.js +++ b/autoindex.js @@ -1,6 +1,7 @@ const allLinks = [...document.getElementsByTagName('a')]; const bodyInner = document.querySelector('.inner'); const tableElem = document.querySelector('table'); +const dirnameElem = document.querySelector('#dirname'); const infoRegex = /(-thumb)?\.(jpg|nfo|srt)$/; let infoFiles = 0; let infoFilesAccounted = 0; @@ -31,19 +32,21 @@ function getHref(tag) { function findByHref(href) { return allLinks.filter((link) => { - return reencodeURI(getHref(link)) === reencodeURI(href) + return reencodeURI(getHref(link)) === reencodeURI(href); }); } function findWholeNumberInText(text, matchNumber) { const numbers = text.match(/\d+/g); - return numbers - && numbers.length - && numbers.map((x) => parseInt(x, 10)).some((number) => number === matchNumber); + return ( + numbers && + numbers.length && + numbers.map((x) => parseInt(x, 10)).some((number) => number === matchNumber) + ); } function findByInfoFile(infoFile) { - const found = ['mkv', 'mp4', 'avi'].reduce((last, current) => { + const found = ['mkv', 'mp4', 'avi', 'webm'].reduce((last, current) => { const element = findByHref(infoFile.replace(infoRegex, '.' + current))[0]; return element || last; }, null); @@ -53,9 +56,20 @@ function findByInfoFile(infoFile) { const pZ = (n) => n.toString().padStart(2, '0'); function restampDateTime(datetime, includeTime = true) { const date = new Date(datetime); - const [month, day, year] = [date.getMonth() + 1, date.getDate(), date.getFullYear()]; - const [hour, minutes, seconds] = [date.getHours(), date.getMinutes(), date.getSeconds()]; - return `${pZ(day)}/${pZ(month)}/${year}${includeTime ? ` ${pZ(hour)}:${pZ(minutes)}:${pZ(seconds)}` : ''}`; + const [month, day, year] = [ + date.getMonth() + 1, + date.getDate(), + date.getFullYear(), + ]; + const [hour, minutes, seconds] = [ + date.getHours(), + date.getMinutes(), + date.getSeconds(), + ]; + return `${year}/${pZ(month)}/${pZ(day)}${ + includeTime ? ` ${pZ(hour)}:${pZ(minutes)}:${pZ(seconds)}` : '' + }`; + // return `${pZ(day)}/${pZ(month)}/${year}${includeTime ? ` ${pZ(hour)}:${pZ(minutes)}:${pZ(seconds)}` : ''}`; } function deduplicateJointEpisode(textContent) { @@ -86,7 +100,12 @@ async function createXMLRequest(nfo) { // Fill metadata boxes, generate buttons function fillMeta( - content, original, nfo, movieTitle, movieDescription, movieButtons + content, + original, + nfo, + movieTitle, + movieDescription, + movieButtons ) { infoFilesAccounted += 1; if (!content) { @@ -105,10 +124,14 @@ function fillMeta( } if (airedEl || premieredEl) { - const timestamp = original.parentElement.parentElement.querySelector('.timestamp'); + const timestamp = + original.parentElement.parentElement.querySelector('.timestamp'); if (timestamp) { const original = timestamp.innerText; - const airdate = restampDateTime((airedEl || premieredEl).textContent, false); + const airdate = restampDateTime( + (airedEl || premieredEl).textContent, + false + ); const wrapper = `${airdate}`; const originalWrapper = ` (${original})`; timestamp.innerHTML = wrapper + originalWrapper; @@ -117,7 +140,7 @@ function fillMeta( const episode = content.querySelector('episode'); const season = content.querySelector('season'); - let title = titleEl.textContent + let title = titleEl.textContent; if (episode && season) { title = `(S${season.textContent} E${episode.textContent}) ` + title; original.parentElement.setAttribute('data-episode', episode.textContent); @@ -144,10 +167,10 @@ function fillMeta( // Add "copy direct url" button if ( - getHref(nfo) !== 'tvshow.nfo' - && navigator - && navigator.clipboard - && navigator.clipboard.writeText + getHref(nfo) !== 'tvshow.nfo' && + navigator && + navigator.clipboard && + navigator.clipboard.writeText ) { const copybutton = document.createElement('button'); copybutton.innerText = 'Copy direct URL'; @@ -169,7 +192,9 @@ function fillMeta( // Replace list entry with a metadata box // Either create or populate function createOrImproveMovieMeta(original, thumbnail, nfo) { - let imgTag = ``; + let imgTag = ``; let movieTitle = `
${original.innerText}
`; let movieDescription = `
`; let movieButtons = `
`; @@ -188,7 +213,7 @@ function createOrImproveMovieMeta(original, thumbnail, nfo) { } } else { original.classList.add('movie'); - original.innerHTML = imgTag + '
' + movieTitle + movieDescription + movieButtons + '
'; + original.innerHTML = `${imgTag}
${movieTitle}${movieDescription}${movieButtons}
`; original.parentElement.classList.add('enhanced'); // quick innerHTML hack to create elements here lol imgTag = original.querySelector('.thumbnail'); @@ -200,7 +225,16 @@ function createOrImproveMovieMeta(original, thumbnail, nfo) { // Fetch metadata from the nfo file and populate the info box if (nfo) { createXMLRequest(reencodeURI(getHref(nfo))) - .then((content) => fillMeta(content, original, nfo, movieTitle, movieDescription, movieButtons)) + .then((content) => + fillMeta( + content, + original, + nfo, + movieTitle, + movieDescription, + movieButtons + ) + ) .catch((e) => { infoFilesAccounted += 1; console.error(e); @@ -236,20 +270,25 @@ function waitUntilInfoComplete() { // Get episode name or name of file const getCellValue = (tr, idx) => - tr.children[idx].getAttribute('data-episode') - || tr.children[idx].innerText - || tr.children[idx].textContent; + tr.children[idx].getAttribute('data-episode') || + tr.children[idx].innerText || + tr.children[idx].textContent; // Compare numeric or (locale-aware, numeric-aware) by string values -const comparer = (idx, asc) => (a, b) => ((v1, v2) => - v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2, 'et', { numeric: true }) - )(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx)); +const comparer = (idx, asc) => (a, b) => + ((v1, v2) => + v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) + ? v1 - v2 + : v1.toString().localeCompare(v2, 'et', { numeric: true }))( + getCellValue(asc ? a : b, idx), + getCellValue(asc ? b : a, idx) + ); // Table sort by column index const sortTableValues = (index) => Array.from(tableElem.querySelectorAll('tr:nth-child(n+2):not(.btn-back)')) - .sort(comparer(index, this.asc = !this.asc)) - .forEach((tr) => tableElem.appendChild(tr)); + .sort(comparer(index, (this.asc = !this.asc))) + .forEach((tr) => tableElem.appendChild(tr)); let tvShow; function accountForMetadata() { @@ -260,10 +299,13 @@ function accountForMetadata() { if (url.match(infoRegex)) { // Hide metadata file link.parentElement.parentElement.style.display = 'none'; - let findOriginal + let findOriginal; // Place TV show information in the beginning of the table if (url === 'tvshow.nfo' || url === 'poster.jpg') { - if (url === 'poster.jpg' && !allLinks.find((found) => getHref(found) === 'tvshow.nfo')) { + if ( + url === 'poster.jpg' && + !allLinks.find((found) => getHref(found) === 'tvshow.nfo') + ) { findOriginal = document.querySelector('a:not([href=".."])'); } else { if (tvShow) { @@ -278,25 +320,29 @@ function accountForMetadata() { findOriginal = tvShow; } } - // Give posters to seasons + // Give posters to seasons } else if (url.startsWith('season') && url.includes('poster')) { // Find season by number const sn = url.match(/season(\d+)/); if (sn) { - findOriginal = allLinks.find( - ({ innerText }) => { - return innerText.endsWith('/') - && findWholeNumberInText(innerText, parseInt(sn[1], 10)); - }); - // Find specials folder + findOriginal = allLinks.find(({ innerText }) => { + return ( + innerText.endsWith('/') && + findWholeNumberInText(innerText, parseInt(sn[1], 10)) + ); + }); + // Find specials folder } else if (url.includes('specials')) { findOriginal = allLinks.find(({ innerText }) => { const matchAgainst = innerText.toLowerCase(); - return (matchAgainst.startsWith('specials') || matchAgainst.startsWith('extras')) - && innerText.endsWith('/'); + return ( + (matchAgainst.startsWith('specials') || + matchAgainst.startsWith('extras')) && + innerText.endsWith('/') + ); }); } - // Find the video file associated with this info file + // Find the video file associated with this info file } else { findOriginal = findByInfoFile(url); } @@ -306,9 +352,10 @@ function accountForMetadata() { if (url.match(/.nfo$/)) { infoFiles += 1; } - createOrImproveMovieMeta(findOriginal, + createOrImproveMovieMeta( + findOriginal, url.match(/.jpg$/) ? link : undefined, - url.match(/.nfo$/) ? link : undefined, + url.match(/.nfo$/) ? link : undefined ); } } @@ -328,11 +375,11 @@ function createToggleCheckbox(field, value, description) { const label = document.createElement('label'); form.className = 'form ' + field; checkbox.type = 'checkbox'; - checkbox.id = field + checkbox.id = field; label.innerText = description; label.setAttribute('for', field); form.appendChild(checkbox); - form.appendChild(label) + form.appendChild(label); bodyInner.appendChild(form); checkbox.addEventListener('change', (e) => { @@ -346,18 +393,42 @@ function createToggleCheckbox(field, value, description) { checkbox.checked = value === 'true'; } +function createDirTree() { + dirnameElem.classList.add('directory'); + const dirSplit = dirnameElem.innerText.split('/').slice(1).filter((item) => !!item); + dirSplit.unshift(''); + dirnameElem.innerHTML = ''; + let lastPath = ''; + for (const dir of dirSplit) { + lastPath += dir + '/'; + const atag = document.createElement('a'); + atag.innerText = dir; + atag.classList.add('directory-link'); + atag.href = lastPath; + dirnameElem.appendChild(atag); + } +} + if ('localStorage' in window) { const plainMode = window.localStorage.getItem('plainMode'); if (plainMode !== 'true') { timestampify(); accountForMetadata(); } - createToggleCheckbox('plainMode', plainMode, 'Disable metadata / display plain index listing (less bandwidth required)'); + createToggleCheckbox( + 'plainMode', + plainMode, + 'Disable metadata / display plain index listing (less bandwidth required)' + ); } else { timestampify(); accountForMetadata(); } -document.querySelectorAll('th').forEach((th) => th.addEventListener('click', (() => { - sortTableValues(Array.from(th.parentNode.children).indexOf(th)); -}))); +document.querySelectorAll('th').forEach((th) => + th.addEventListener('click', () => { + sortTableValues(Array.from(th.parentNode.children).indexOf(th)); + }) +); + +createDirTree();