From 50109efaff0c7ee8f9d7073e60c94e88bdc73584 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 + public/Potesta_&_Associates/README.md | 40 + public/README.md | 78 +- .../@russellcountyvirginia8228/README.md | 6 - 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 +- 20 files changed, 4457 insertions(+), 211 deletions(-) create mode 100644 public/Potesta_&_Associates/README.md delete mode 100644 public/Russell_County_BOS/YouTube_Archive/@russellcountyvirginia8228/README.md 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/public/Potesta_&_Associates/README.md b/public/Potesta_&_Associates/README.md new file mode 100644 index 00000000..7b5967c4 --- /dev/null +++ b/public/Potesta_&_Associates/README.md @@ -0,0 +1,40 @@ +# Potesta & Associates + +Russell County Board of Supervisors has hired Potesta & Associates to study the feasibility of a landfill at the Moss 3 site that Russell County Reclamation, LLC. + +## History and Overview + +Potesta & Associates, Inc. (POTESTA) is an engineering and environmental consulting firm with offices in Charleston, West Virginia, Morgantown, West Virginia, and Winchester, Virginia. Since its establishment in 1997, POTESTA has provided professional services to a wide range of clients across the eastern United States. + +## Engagement with Russell County + +The company conducted an evaluation of information provided by Russell County Reclamation, LLC (RCR) regarding the proposed landfill project for Russell County. They were hired by the Board of Supervisors. + +## Evaluation of Landfill Feasibility + +Potesta & Associates conducted a review of the site, including an analysis of historical land use, geological conditions, and regulatory requirements. The evaluation focused on factors such as floodplains, geological stability, proximity to water sources, and site characteristics affecting landfill construction and operation. + +## Engineering Considerations + +The evaluation identified several site conditions requiring further engineering analysis, including the stability of coal refuse areas, evaluation of underground mine workings, location of disposal unit boundaries, and assessment of existing coal refuse disposal dams. Potesta & Associates emphasized the importance of addressing these considerations to ensure the environmentally sound development and operation of the landfill. + +## Conclusion and Recommendations + +Potesta & Associates concluded that, at the time of the evaluation, no issues were identified that would render the site "unpermittable" for landfill development. However, the company recommended that Russell County obtain additional detailed information from RCR regarding the identified issues and their proposed solutions before moving forward with the project. + +## Invoices + +The following invoices have been presented by the County Administrator: + +| Invoice # | Invoice Date | Due Date | Gross Amount +| --------- | ------------ | ---------- | ------------ +| 166028 | 1/18/2023 | 2/6/2023 | $22,438.87 +| 167130 | 8/23/2023 | 9/5/2023 | $14,912.18 +| 167404 | 10/18/2023 | 11/13/2023 | $8,828.31 +| 167602 | 11/16/2023 | 12/11/2023 | $5,113.48 +| 167730 | 12/17/2023 | 1/2/2024 | $180.00 +| **Total** | | | **$51,472.84** + +## Documents + +The following documents have been provided by Russell County: diff --git a/public/README.md b/public/README.md index dc6c9646..37397e53 100644 --- a/public/README.md +++ b/public/README.md @@ -1,31 +1,77 @@ # Contents ## [Potesta & Associates](/Potesta_&_Associates) +Potesta & Associates has been retained by the Russell County Board of Supervisors to study the feasibility of a landfill. -## [Russell County Board of Supervisors](/Russell_County_BOS) -+ ### [Meetings](/Russell_County_BOS/Meetings) - + #### [Agenda Packets](/Russell_County_BOS/Meetings/Agenda_Packets) - + #### [Agendas](/Russell_County_BOS/Meetings/Agendas) - + #### [Minutes](/Russell_County_BOS/Meetings/Minutes) -+ ### [Ordinances](/Russell_County_BOS/Ordinances) - + #### [Solid Waste](/Russell_County_BOS/Ordinances/Solid_Waste) -+ ### [YouTube Archive](/Russell_County_BOS/YouTube_Archive) - + #### [@russellcountyvirginia8228](/Russell_County_BOS/YouTube_Archive/@russellcountyvirginia8228) +## [Russell County](/Russell_County) +Russell County is a county located in the Commonwealth of Virginia. As of the 2020 census, the population was 25,781. Its county seat is Lebanon. -## [Russell County Industrial Development Authority](/Russell_County_IDA) -+ ### [Meetings](/Russell_County_IDA/Meetings) - + #### [Agenda Packets](/Russell_County_IDA/Meetings/Agenda_Packets) ++ ### [Audit and Budget Information](/Russell_County/Audit_and_Budget_Information) + Annual audits and budget proposals for Russell County. -## [Russell County Tourism](/Russell_County_Tourism) -+ ### [Meetings](/Russell_County_Tourism/Meetings) - + #### [Agenda](/Russell_County_Tourism/Meetings/Agenda) - + #### [Minutes](/Russell_County_Tourism/Meetings/Minutes) ++ ### [Board of Supervisors](/Russell_County/Board_of_Supervisors) + The Board of Supervisors is a governmental body that oversees the operation of the Russell County government in the Commonwealth of Virginia. + + + #### [Meetings](/Russell_County/Board_of_Supervisors/Meetings) + The Board of Supervisors have regular meetings. These meetings are normally streamed live on YouTube. + + + ##### [Agenda Packets](/Russell_County/Board_of_Supervisors/Meetings/Agenda_Packets) + + + ##### [Agendas](/Russell_County/Board_of_Supervisors/Meetings/Agendas) + + + ##### [Minutes](/Russell_County/Board_of_Supervisors/Meetings/Minutes) + + + #### [YouTube Archive](/Russell_County/Board_of_Supervisors/YouTube_Archive) + + + ##### [@russellcountyvirginia8228](/Russell_County/Board_of_Supervisors/YouTube_Archive/@russellcountyvirginia8228) + ++ ### [Industrial Development Authority](/Russell_County/Industrial_Development_Authority) + The Industrial Development Authority is the primary organization responsible for economic and industrial development in Russell County. They are appointed by the Board of Supervisors. + + + #### [Meetings](/Russell_County/Industrial_Development_Authority/Meetings) + The Industrial Development Authority have regular meetings. There are no known recordings of these meetings, except for the approved minutes which are contained within the agenda packets. + + + ##### [Agenda Packets](/Russell_County/Industrial_Development_Authority/Meetings/Agenda_Packets) + ++ ### [Ordinances](/Russell_County/Ordinances) + + + #### [Cigarette](/Russell_County/Ordinances/Cigarette) + + + #### [Food and Beverage Tax](/Russell_County/Ordinances/Food_and_Beverage_Tax) + + + #### [Noise](/Russell_County/Ordinances/Noise) + + + #### [Redistricting](/Russell_County/Ordinances/Redistricting) + + + #### [Solid Waste](/Russell_County/Ordinances/Solid_Waste) + There are solid waste ordinances from + [1984](/Russell_County/Ordinances/Solid_Waste/1984-04-03_Amendment_to_Solid_Waste_Ordinance.pdf), + [1985](/Russell_County/Ordinances/Solid_Waste/1985-11-01_Solid_Waste_Ordinance.pdf), + [1991](/Russell_County/Ordinances/Solid_Waste/1991-11-18_Solid_Waste_Management_Facility_Prohibition_and_Siting_Ordinance.pdf), + [2010](/Russell_County/Ordinances/Solid_Waste/2010_12_06_Russell_County_Solid_Waste_Management_Ordinance.pdf), and + [2017](/Russell_County/Ordinances/Solid_Waste/2017-03-13_Russell_County_Solid_Waste_Management_Ordinance.pdf). + The language in 2010 alludes to only conditional prohibition ("unless permitted") of private solid waste management facilities, a + departure from the 1991 language where private solid waste management facilities were prohibited. + ++ ### [Tourism Committee](/Russell_County/Tourism_Committee) + + + #### [Meetings](/Russell_County/Tourism_Committee/Meetings) + + + ##### [Agenda](/Russell_County/Tourism_Committee/Meetings/Agenda) + + + ##### [Minutes](/Russell_County/Tourism_Committee/Meetings/Minutes) ## [United Mine Workers of America](/United_Mine_Workers_of_America) +United Mine Workers of America adamantly oppose the Moss 3 Landfill. + + ### [Art](/United_Mine_Workers_of_America/Art) + + ### [Press Releases](/United_Mine_Workers_of_America/Press_Releases) ## [Virginia Energy](/Virginia_Energy) + + ### [Russell County Reclamation, LLC](/Virginia_Energy/Russell_County_Reclamation_LLC) + + ### [YouTube Archive](/Virginia_Energy/YouTube_Archive) + + #### [@VADMME](/Virginia_Energy/YouTube_Archive/@VADMME) diff --git a/public/Russell_County_BOS/YouTube_Archive/@russellcountyvirginia8228/README.md b/public/Russell_County_BOS/YouTube_Archive/@russellcountyvirginia8228/README.md deleted file mode 100644 index 7af04dde..00000000 --- a/public/Russell_County_BOS/YouTube_Archive/@russellcountyvirginia8228/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# YouTube Video Archives - -These videos are archives of the videos released to the YouTube channel for Russell County Board of Supervisors. - -The original channel for these videos is -[@russellcountyvirginia8228](https://www.youtube.com/@russellcountyvirginia8228). 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 @@