From 9dbe87e1667b22efd1fa26c1a6f83e61b5847f2e Mon Sep 17 00:00:00 2001 From: David Ball Date: Thu, 25 Apr 2024 14:48:45 -0400 Subject: [PATCH] Updated design, added Video feature, working on documents feature. --- app/server.js | 81 +- app/vercel-serve.js | 5 +- gulpfile.js | 180 +- package.json | 1 + static/css/nm3clol.css | 230 ++ static/svg/no-trash.svg | 3796 ++++++++++++++++++++++++++++++ sync-youtube-videos.cmd | 5 + views/directory.ejs | 14 +- views/helpers/functions.js | 3 +- views/includes/bottom-navbar.ejs | 2 +- views/includes/common-head.ejs | 140 +- views/includes/no-trash-svg.ejs | 18 + views/includes/top-navbar.ejs | 11 +- views/page.ejs | 5 +- views/search-error.ejs | 4 +- views/search-results.ejs | 6 +- views/video-player.ejs | 43 +- 17 files changed, 4355 insertions(+), 189 deletions(-) create mode 100644 static/css/nm3clol.css create mode 100644 static/svg/no-trash.svg create mode 100644 sync-youtube-videos.cmd create mode 100644 views/includes/no-trash-svg.ejs diff --git a/app/server.js b/app/server.js index baccc8da..d7321a2b 100644 --- a/app/server.js +++ b/app/server.js @@ -8,6 +8,7 @@ const matter = require('gray-matter'); const ejs = require('ejs'); const helpers = require('../views/helpers/functions'); const search = require('../routes/search'); +const fs = require('fs'); // const advancedSearch = require('../routes/advanced-search'); // Port number for HTTP server @@ -57,7 +58,7 @@ Allow: / }); // Search endpoints -console.log("Setting route for /search"); +console.log("Setting routes for /search"); app.use('/search', search.router); // app.use('/advanced-search', advancedSearch.router); @@ -85,9 +86,32 @@ glob.globSync('pages/**/*.md', { }); }); +console.log("Scanning for documents to create routes"); +glob.globSync('**/*{.pdf,.docx,.xlsx,.pptx,.doc,.xls,.ppt}', { + cwd: path.join(__dirname, '..', 'public'), + matchBase: true, + follow: true, +}).forEach((filePath) => { + const expressRoutePathFromFilePath = (filePath) => { + return filePath.substring(0, filePath.length - path.extname(filePath).length).replaceAll(path.sep, path.posix.sep); + }; + const route = expressRoutePathFromFilePath(filePath); + const fullFilePath = path.join(__dirname, '..', 'public', filePath); + let paths = route.split(path.posix.sep); + paths[0] = 'public'; + console.log(`Setting route for ${route}`); + app.get(route, async (req, res) => { + const fm = matter.read(fullFilePath); + const fmData = { fm: fm.data, excerpt: fm.excerpt }; + const content = helpers.md.render(fm.content, fmData ); + const renderData = { content, route, filePath, fullFilePath, req, paths, ...fmData }; + res.render("page", { h: helpers, ...renderData }); + }); +}); + // Endpoints for all the site's YouTube videos. console.log("Scanning for archived videos to create routes"); -glob.globSync('Russell_County/Board_of_Supervisors/YouTube_Archive/**/*.info.json', { +glob.globSync(['Russell_County/Board_of_Supervisors/YouTube_Archive/**/*.info.json', 'Virginia_Energy/YouTube_Archive/**/*.info.json'], { cwd: path.join(__dirname, '..', 'public'), matchBase: true, follow: true, @@ -99,16 +123,17 @@ glob.globSync('Russell_County/Board_of_Supervisors/YouTube_Archive/**/*.info.jso return filePath.substring(0, filePath.lastIndexOf(path.sep)); } const directory = dirFromFilePath(filePath); - let videoURL = glob.globSync("*.{mpg,mpeg,mp4,mkv,webm}", { + let videoURL = ""+glob.globSync("*.{mpg,mpeg,mp4,mkv,webm}", { cwd: path.join(__dirname, '..', 'public', directory), matchBase: true, follow: true, }).pop(); - let subtitleURL = glob.globSync("*.en.vtt", { + let subtitleURL = ""+glob.globSync("*.en.vtt", { cwd: path.join(__dirname, '..', 'public', directory), matchBase: true, follow: true, }).pop(); + let subtitleFile = path.join(__dirname, '..', 'public', directory, subtitleURL); const route = encodeURI(expressRoutePathFromFilePath(filePath)); let paths = filePath.substring(0, filePath.lastIndexOf(path.sep) > 0 ? filePath.lastIndexOf(path.sep) : filePath.length-1).split(path.sep); paths = paths.map((name, idx, aPaths) => { @@ -122,13 +147,48 @@ glob.globSync('Russell_County/Board_of_Supervisors/YouTube_Archive/**/*.info.jso console.log(`Setting route for ${route}`); app.get(route, async (req, res) => { let info = require(fullFilePath); - const renderData = { route, filePath, fullFilePath, req, paths, directory, videoURL, subtitleURL, info }; - res.render("video-player", { h: helpers, ...renderData }); + let subtitleVTT = fs.existsSync(subtitleFile)?fs.readFileSync(subtitleFile, 'utf8'):undefined; + const renderData = { route, filePath, fullFilePath, req, paths, directory, videoURL, subtitleURL, subtitleVTT, info }; + res.render("video-player", { h: helpers, require, ...renderData }); }); }); //app.get('/OCR-Encoded-PDFs/Russell-County-Web-Site_2024-02-13_19_50_Modified-With-OCR-Encoding**', rewriter.rewrite('/Web_Site_Archives/Russell_County_Web_Site-2024-02-13_19_50_Modified_With_OCR_Encoding/$1')); +console.log(`Setting routes for /css/*.css`);; +app.get('/css/*.css', async (req, res) => { + await serve(req, res, { + public: path.join(__dirname, '..', 'static'), + symlinks: true, + trailingSlash: true, + cleanUrls: false, + renderSingle: false, + unlisted: [ + ".DS_Store", + ".git", + "Thumbs.db", + // "README*", + ], + }); +}); + +console.log(`Setting routes for /svg/*.svg`);; +app.get('/svg/*.svg', async (req, res) => { + await serve(req, res, { + public: path.join(__dirname, '..', 'static'), + symlinks: true, + trailingSlash: true, + cleanUrls: false, + renderSingle: false, + unlisted: [ + ".DS_Store", + ".git", + "Thumbs.db", + // "README*", + ], + }); +}); + console.log(`Setting route for *`); app.get('*', async (req, res) => { await serve(req, res, { @@ -140,7 +200,8 @@ app.get('*', async (req, res) => { unlisted: [ ".DS_Store", ".git", - "README*" + "Thumbs.db", + // "README*", ], redirects: [ { @@ -159,9 +220,9 @@ app.get('*', async (req, res) => { source: "/OCR-Encoded-PDFs/Russell-County-Web-Site_2024-02-13_19_50_Modified-With-OCR-Encoding/:u(.*)", destination: "/Web_Site_Archives/Russell_County_Web_Site-2024-02-13_19_50_Modified_With_OCR_Encoding:u" }, - { source: '/YouTube Channel', destination: '/Russell_County_BOS/YouTube_Channel' }, - { source: '/YouTube Channel.zip', destination: '/Russell_County_BOS/YouTube_Channel.zip' }, - { source: '/YouTube Channel/:u?', destination: '/Russell_County_BOS/YouTube_Channel/:u' }, + { source: '/YouTube Channel', destination: '/Russell_County/Board_of_Supervisors/YouTube_Channel' }, + // { source: '/YouTube Channel.zip', destination: '/Russell_County_BOS/YouTube_Channel.zip' }, + // { source: '/YouTube Channel/:u?', destination: '/Russell_County_BOS/YouTube_Channel/:u' }, { source: '/Project Reclaim [WI19KR9Ogwg].mkv', destination: '/YouTube_Archives/@VADMME/Project Reclaim [WI19KR9Ogwg].mkv' }, ] }); diff --git a/app/vercel-serve.js b/app/vercel-serve.js index b92ebb6c..d59b25a8 100644 --- a/app/vercel-serve.js +++ b/app/vercel-serve.js @@ -48,7 +48,10 @@ const errorTemplate = (vals) => { const isDirectoryOrDirectorySymbolicLink = (path, max_recursion_depth = 10, cb) => { lstat(path, {}, (err, sym_stats) => { - if (sym_stats.isSymbolicLink() && max_recursion_depth > 0) { + if (err) { + cb(err); + } + else if (sym_stats.isSymbolicLink() && max_recursion_depth > 0) { readlink(path, {}, (err, path) => { isDirectoryOrDirectorySymbolicLink(path, max_recursion_depth-1, cb); }); diff --git a/gulpfile.js b/gulpfile.js index 51d3946c..e74fe44f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,6 +13,7 @@ const relPathToFiles = './public'; const baseUrl = 'https://no-moss-3-carbo-landfill-library.online'; // URL of the document to download and index const tikaUrl = 'http://solr.services.cleveland.daball.me:9998'; // URL of the Tika instance const solrUrl = 'http://solr.services.cleveland.daball.me:8983/solr/my_core'; // URL of your Solr instance +const solrVirginiaLawUrl = 'http://solr.services.cleveland.daball.me:8983/solr/va_code'; // URL of your Solr instance // Task to clear out previous Solr data gulp.task('index:clear', async () => { @@ -44,6 +45,19 @@ async function calculateSHA256Hash(filePath) { }); } +// Function to retrieve metadata of a file from Solr +async function retrieveVirginiaLawMetadataFromSolr(url) { + // Retrieve metadata from Solr based on the file URL or unique identifier + // const response = await axios.get(`${solrUrl}/select?q=id:"${encodeURIComponent(url)}"&fl=${encodeURIComponent('sha256sum, content_length')}`, { + // responseType: 'json' + // }); + const fl = encodeURIComponent("sha256sum, content_length"); + const q = encodeURIComponent("id:")+"\""+encodeURIComponent(url)+"\"";//encodeURIComponent(`id:"${url}"`); + const uri = `${solrVirginiaLawUrl}/select?q=${q}&fl=${fl}`; + const response = await request({ uri: `${uri}`, json: true }); + return response && response.response && response.response.docs && response.response.docs[0]; +} + // Function to retrieve metadata of a file from Solr async function retrieveMetadataFromSolr(url) { // Retrieve metadata from Solr based on the file URL or unique identifier @@ -71,6 +85,20 @@ async function indexDocumentInSolr(document) { } } +async function indexLawDocumentInSolr(document) { + try { + // Send document to Solr using the Solr REST API or a Solr client library + // Example code to send document using Axios: + await axios.post(solrVirginiaLawUrl + '/update/json/docs', document, { + params: { + commit: true, // Commit changes immediately + }, + }); + } catch (error) { + throw new Error('Error indexing document in Solr: ' + error.message); + } +} + function extToMime(file_name) { switch (path.extname(file_name)) { case '.htm': @@ -80,6 +108,8 @@ function extToMime(file_name) { return 'application/pdf'; case '.md': case '.txt': + case '.mkv': + return 'video/x-matroska'; default: return 'text/plain'; } @@ -87,17 +117,12 @@ function extToMime(file_name) { // Task to index files into Solr -gulp.task('index:docs', async () => { +gulp.task('index:laws', async () => { + //let scanExts = ''; //set to empty string to scan all + let scanExts = '.{pdf,docx,pptx,xlsx,jpg,png,txt}'; let globs = [ - 'Potesta_&_Associates/**/*.{pdf, docx, jpg, png, txt}', - // 'Russell_County_BOS/Documents/**/*.{pdf, docx, jpg, png, txt}', - 'Russell_County_BOS/Meetings/**/*.{pdf, docx, jpg, png, txt}', - 'Russell_County_BOS/Ordinances/**/*.{pdf, docx, jpg, png, txt}', - 'Russell_County_IDA/Meetings/**/*.{pdf, docx, jpg, png, txt}', - 'Russell_County_Tourism/Agenda/**/*.{pdf, docx, jpg, png, txt}', - 'Russell_County_Tourism/Minutes/**/*.{pdf, docx, jpg, png, txt}', - 'United_Mine_Workers_of_America/**/*.{pdf, docx, jpg, png, txt}', - 'Virginia_Energy/**/*.{pdf, docx, jpg, png, txt}', + `Russell_County/Ordinances/**/*${scanExts}`, + `Virginia_Law_Library/**/*${scanExts}`, ]; // Use glob to match files in the local directories let files = []; @@ -121,7 +146,7 @@ gulp.task('index:docs', async () => { console.log('URL: ' + url); // Retrieve metadata of the file from Solr (if it exists) - const metadata = await retrieveMetadataFromSolr(url); + const metadata = await retrieveVirginiaLawMetadataFromSolr(url); // Calculate file size const stats = fs.statSync(fileFullPath); @@ -152,7 +177,7 @@ gulp.task('index:docs', async () => { }); // Use the TikaClient's pipe method to extract text content - await client.pipe(f, writableStream, 'text/plain', path.basename(file)); + await client.pipe(f, writableStream, 'text/plain', encodeURI(path.basename(file))); console.log("Extracted Text:", extractedText); // Create Solr document @@ -167,6 +192,129 @@ gulp.task('index:docs', async () => { // Add additional fields as needed (e.g., title, author, etc.) }; + // Send document to Solr for indexing + // Index the file with its text content and metadata + console.log(`Indexing ${url}`); + await indexLawDocumentInSolr(solrDocument); + + // Continue + console.log(`Done.`); + } else { + // Metadata matches, skip the file + console.log(`Skipping file '${file}' as metadata matches existing metadata in Solr index.`); + } + } +}); + +// Task to index files into Solr +gulp.task('index:docs', async () => { + //let scanExts = ''; //set to empty string to scan all + let scanExts = '.{pdf,docx,pptx,xlsx,jpg,png,txt,mkv}'; + let globs = [ + `Amys_Drop_Box/**/*${scanExts}`, + `CRS_Reports/**/*${scanExts}`, + `Mine_Safety_and_Health_Administration/**/*${scanExts}`, + `Potesta_&_Associates/**/*${scanExts}`, + `Russell_County/**/*${scanExts}`, + `Russell_County_Reclamation_LLC/**/*${scanExts}`, + `Tobacco_Region_Revitalization_Commission/**/*${scanExts}`, + `United_Mine_Workers_of_America/**/*${scanExts}`, + `Virginia_Energy/**/*${scanExts}`, + // I want to put Virginia Law in its own search category first. + // `Virginia_Law_Library/**/*${scanExts}`, + ]; + // Use glob to match files in the local directories + let files = []; + let cwd = path.resolve(__dirname, relPathToFiles.replaceAll('/', path.sep)); + globs.forEach(async (globPattern) => { + files = files.concat(glob.globSync(globPattern, { + cwd, + matchBase: true, + follow: true, + })); + }); + console.log(`Found ${files.length} files to index using ${globs.length} glob patterns.`); + // Loop through each file and process them + for (let f = 0; f < files.length; f++) { + const file = files[f]; + console.log(`${f+1}/${files.length}: ${file}`); + + const fileFullPath = path.join(cwd, file); + + let url = `https://no-moss-3-carbo-landfill-library.online/${file.replaceAll(path.sep, '/')}`; + console.log('URL: ' + url); + + // Retrieve metadata of the file from Solr (if it exists) + const metadata = await retrieveMetadataFromSolr(url); + + // Calculate file size + const stats = fs.statSync(fileFullPath); + const fileSize = stats.size; + + // Calculate SHA256 checksum + // const checksum = crypto.createHash('sha256').update(fileContents).digest('hex'); + const checksum = await calculateSHA256Hash(fileFullPath); + + // Compare metadata + if (!metadata || parseInt(metadata.content_length[0]) != fileSize || metadata.sha256sum[0] != checksum) { + // Metadata mismatch or file not found in Solr, proceed with indexing + console.log(`Processing text from file using Tika.`); + const client = new TikaClient({ host: tikaUrl }); + const version = await client.getVersion(); + console.info(`Tika Server Version: ${version}`); + + let extractedText = ''; + + let subtitleExt = ".en.vtt"; + if (url.endsWith(".webm") || url.endsWith(".mkv") || url.endsWith(".mpg") || url.endsWith(".mpeg") || url.endsWith(".mp4")) { + let subtitleFilePath = fileFullPath.substring(0, fileFullPath.lastIndexOf('.')) + subtitleExt; + if (fs.existsSync(subtitleFilePath)) { + console.log("Found VTT subtitle file at:", subtitleFilePath); + extractedText = fs.readFileSync(subtitleFilePath, 'utf8'); + url = url.substring(0, url.lastIndexOf('/')+1); + } + else { + console.log("No subtitles found at: ", subtitleFilePath); + console.log("Skipping this video file. Not adding this to the index until subtitles are available.") + continue; + } + } + else { + // Create a Readable stream for the file contents + let f = fs.createReadStream(fileFullPath); + // Create a writable stream to capture the extracted text content into a string + const writableStream = new Writable({ + write(chunk, encoding, callback) { + extractedText += chunk.toString(); // Append the chunk to the extracted text + callback(); + } + }); + // Use the TikaClient's pipe method to extract text content + await client.pipe(f, writableStream, 'text/plain', encodeURI(path.basename(file))); + } + if (!extractedText) { + console.log("Skipping document because no text was detected."); + continue; + } + else if (extractedText.length < 100) { + console.log("Extracted Text:", extractedText); + } + else { + console.log("Extracted Text (excerpt):", extractedText.substring(0, 99)); + } + + // Create Solr document + const solrDocument = { + id: url, // Replace with a unique identifier for the document + text: extractedText, // Add the extracted text content + sha256sum: checksum, // Add the checksum + //html: response.data, + url: url, + content_length: fileSize, + content_type: extToMime(url), + // Add additional fields as needed (e.g., title, author, etc.) + }; + // Send document to Solr for indexing // Index the file with its text content and metadata console.log(`Indexing ${url}`); @@ -181,11 +329,11 @@ gulp.task('index:docs', async () => { } }); -// Task to optionally run both clearing and indexing -gulp.task('index:reindex', gulp.series('index:clear', 'index:docs')); - // Default task to run indexing -gulp.task('index', gulp.series('index:docs')); +gulp.task('index', gulp.series('index:docs', 'index:laws')); + +// Task to optionally run both clearing and indexing +gulp.task('index:reindex', gulp.series('index:clear', 'index')); // Default task to run indexing gulp.task('default', gulp.series('index')); diff --git a/package.json b/package.json index 178f59cc..f4ed7a19 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "index": "gulp index", "index:clear": "gulp index:clear", "index:docs": "gulp index:docs", + "index:laws": "gulp index:laws", "index:reindex": "gulp index:reindex" }, "author": "", diff --git a/static/css/nm3clol.css b/static/css/nm3clol.css new file mode 100644 index 00000000..ca3b6a35 --- /dev/null +++ b/static/css/nm3clol.css @@ -0,0 +1,230 @@ +.result-highlight { background-color: #FBF719; font-weight: normal; } +.pt-1500 { padding-top: 100vh; } +:root { + --bs-body-font-size: 1.2rem !important; +} +body { + margin: 0; + background: #fff; + font-family: "Saira Extra Condensed", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; +} +p, li { + font-family: "Noto Serif", "Times New Roman", Times, serif; +} +#files li { + font-family: inherit; +} +h1, h2, h3, h4, h5, h6 { + font-family: "Alegreya SC", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-weight: 700; +} +.list-group-item { + background-color: transparent; +} +.list-group-item-action:focus,.list-group-item-action:hover { + background-color: rgba(244, 67, 54, 0.75); + color: #fff; +} +.list-group-item-action:focus a,.list-group-item-action:hover a:visited { + background-color: rgba(244, 67, 54, 0.75); + color: #fff; +} +.list-group-item-action:focus a:focus,.list-group-item-action:hover a:hover { + color: #fff !important; + text-decoration: underline; +} +.bg-primary { + background-color: #f44336 !important; +} +.nmc3clol-navbar-brand { + font-family: "Cinzel Decorative"; + text-transform: capitalize !important; +} +.daball-navbar-brand { + font-family: "Saira Extra Condensed"; +} +main { + max-width: 100vw; + margin-top: 50px; +} +header { + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} +.nm3clol-navbar-brand, .navbar { + font-size: 1.2rem !important; +} +@media (min-width: 0px) and (max-width: 576px) { + .nm3clol-navbar-brand, .navbar { + font-size: 0.55rem !important; + } +} +@media (min-width: 576px) and (max-width: 768px) { + .nm3clol-navbar-brand, .navbar { + font-size: 0.75rem !important; + } +} +@media (min-width: 768px) and (max-width: 1200px) { + .nm3clol-navbar-brand, .navbar { + font-size: 1.0rem !important; + } +} +.btn-outline-search { + color: #fff; + border-color: #fff; +} +.btn-outline-search:hover { + color: #f44336; + border-color: #f44336; + background-color: rgba(255, 255, 255, 0.75); +} +h1 i { + font-style: normal; +} +ul#files { + margin: 0 0 0 -2px; + padding: 20px 0 0 0; +} +ul#files li { + list-style: none; + font-size: 14px; + display: flex; + justify-content: space-between; +} +a { + text-decoration: none; +} +ul#files a { + color: #000; + padding: 10px 5px; + margin: 0 -5px; + white-space: nowrap; + overflow: hidden; + display: block; + width: 100%; + text-overflow: ellipsis; +} +a { + color: #f44336; + display: inline-block; + line-height: 20px; +} +a:hover, a:active { + color: #0076FF; + display: inline-block; + line-height: 20px; +} +a .pretty, a .cool { + display: none; +} +a:hover .david, a:active .david, a:hover .cool, a:active .cool { + display: inline; + color: #0076FF; +} +a:hover .allen, a:active .allen { + display: inline; + color: #fff; +} +a:hover .ball, a:active .ball, a:hover .pretty, a:active .pretty { + display: inline; + color: #f44336; +} +svg { + height: 13px; + vertical-align: text-bottom; +} +ul#files a::before { + display: inline-block; + vertical-align: middle; + margin-right: 10px; + width: 24px; + text-align: center; + line-height: 12px; +} +/* file-icon – svg inlined here, but it should also be possible to separate out. */ +ul#files a.file::before { + content: url("data:image/svg+xml;utf8,"); +} +ul#files a.file:hover::before { + content: url("data:image/svg+xml;utf8,"); + text-decoration: underline; +} +/* folder-icon */ +ul#files a.folder::before { + content: url("data:image/svg+xml;utf8,"); +} +ul#files a.folder:hover::before { + content: url("data:image/svg+xml;utf8,"); +} +/* image-icon */ +ul#files a.file.gif::before, +ul#files a.file.jpg::before, +ul#files a.file.png::before, +ul#files a.file.svg::before { + content: url("data:image/svg+xml;utf8,"); +} +::selection { + background-color: #f44336; + color: #fff; +} +::-moz-selection { + background-color: #f44336; + color: #fff; +} +header h1 { + font-size: 2em; +} + +header h1 .separator { + font-size: 4.0rem; + line-height: 1rem; + vertical-align: -.75vh; +} +@media (max-width: 1200px) { + header h1 { + font-size: 1.5rem; + } + header h1 .separator { + font-size: 2em; + line-height: 1em; + vertical-align: -.75vh; + } +} +@media (min-width: 768px) { + ul#files { + display: flex; + flex-wrap: wrap; + } +} +@media (min-width: 992px) { + /* body { + padding: 45px; + } */ + ul#files li { + font-size: 16pt; + box-sizing: border-box; + justify-content: flex-start; + } +} +img.no-trash-svg { + z-index: -10000; + position: fixed; + width: 1.5%; + opacity: 0.04; + top: 50%; + left: 50%; + margin-top: -0.5vh; + margin-left: -1.5vh; + transform: scale(35, 35); +} + +table { + margin-bottom: 5pt; +} + +tbody, td, tfoot, th, thead, tr { + font-family: 'Sometype Mono'; + padding: 5pt; +} \ No newline at end of file diff --git a/static/svg/no-trash.svg b/static/svg/no-trash.svg new file mode 100644 index 00000000..1323f5c7 --- /dev/null +++ b/static/svg/no-trash.svg @@ -0,0 +1,3796 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sync-youtube-videos.cmd b/sync-youtube-videos.cmd new file mode 100644 index 00000000..5f457f07 --- /dev/null +++ b/sync-youtube-videos.cmd @@ -0,0 +1,5 @@ +@echo off +S:\bin\yt-dlp.exe -U +S:\bin\yt-dlp.exe --live-from-start --yes-playlist -N 8 -R infinite -c --no-force-overwrites --mtime --write-description --write-info-json --write-playlist-metafiles --write-comments --no-cookies-from-browser --cookies S:\srv\www\no-moss-3-carbo-landfill-library.online\youtube-cookies.txt --write-thumbnail --write-all-thumbnails --write-url-link --write-webloc-link --write-desktop-link --progress --video-multistreams --audio-multistreams --write-subs --write-auto-subs --embed-subs --embed-thumbnail --embed-metadata --embed-chapters --embed-info-json -o "S:\srv\www\no-moss-3-carbo-landfill-library.online\YouTube\%%(uploader_id)s\%%(upload_date>%%Y-%%m-%%d)s-%%(title)s\%%(id)s.%%(ext)s" "https://www.youtube.com/@russellcountyvirginia8228" +S:\bin\yt-dlp.exe --live-from-start --yes-playlist -N 8 -R infinite -c --no-force-overwrites --mtime --write-description --write-info-json --write-playlist-metafiles --write-comments --no-cookies-from-browser --cookies S:\srv\www\no-moss-3-carbo-landfill-library.online\youtube-cookies.txt --write-thumbnail --write-all-thumbnails --write-url-link --write-webloc-link --write-desktop-link --progress --video-multistreams --audio-multistreams --write-subs --write-auto-subs --embed-subs --embed-thumbnail --embed-metadata --embed-chapters --embed-info-json -o "S:\srv\www\no-moss-3-carbo-landfill-library.online\YouTube\%%(uploader_id)s\%%(upload_date>%%Y-%%m-%%d)s-%%(title)s\%%(id)s.%%(ext)s" "https://www.youtube.com/@VADMME" +npm run-script index:docs diff --git a/views/directory.ejs b/views/directory.ejs index 018e9268..a5b9f445 100644 --- a/views/directory.ejs +++ b/views/directory.ejs @@ -9,21 +9,23 @@ <%- include('./includes/top-navbar.ejs') %> + <%- include('./includes/no-trash-svg.ejs') %> +
-

-   +

<% paths.forEach(function(value, index) { %> <% if (h.shouldShowDirectorySeparator({index})) { %> <% } %> <% if (h.shouldShowWelcomeBanner({paths})) { %> - Welcome to <%= h.getDirectoryTitle({directory}) %> +   + Get Informed! Stay Informed! <% } else if (h.shouldOmitLinkOnLastBreadcrumb({paths, index})) { %> - <%= h.trimSlashes({path: value.name}) %> + <%= h.trimSlashes({path: value.name}).replaceAll('_', ' ') %> <% } else { %> - <%= h.getDirectoryName({directory: value.name}) %> + <%= h.getDirectoryName({directory: value.name}).replaceAll('_', ' ') %> <% } %> <% }); %> @@ -32,7 +34,7 @@ <% if (h.directoryContainsReadme({directory})) {%>
-
+
<%- h.printReadme({directory}) %>
diff --git a/views/helpers/functions.js b/views/helpers/functions.js index a66fc850..98d9ca3f 100644 --- a/views/helpers/functions.js +++ b/views/helpers/functions.js @@ -36,7 +36,8 @@ const getDirectoryTitle = ({directory}) => { return (directory=="public") ? getSiteName() : `${title} Listing - ${getSiteName()}`; }; const getWelcomeBanner = ({directory}) => { - return trimSlashes({path: directory.replace("public", `Welcome to ${getSiteName()}`)}); + //return trimSlashes({path: directory.replace("public", `Welcome to ${getSiteName()}`)}); + return "Get Informed! Stay Informed!"; }; const shouldShowDirectorySeparator = ({index}) => (index > 0); const shouldShowWelcomeBanner = ({paths}) => (paths.length == 1); diff --git a/views/includes/bottom-navbar.ejs b/views/includes/bottom-navbar.ejs index 76429c02..5d2df91c 100644 --- a/views/includes/bottom-navbar.ejs +++ b/views/includes/bottom-navbar.ejs @@ -1,6 +1,6 @@