Merge pull request 'main' (#15) from daball/nm3clol-express-app:main into main

Reviewed-on: nm3clol/nm3clol-express-app#15
This commit is contained in:
David Ball 2024-06-24 01:03:50 -04:00
commit b38d7ee07e
22 changed files with 620 additions and 595 deletions

View File

@ -2,188 +2,186 @@
* The `ProcessEnv` interface represents the imported environment variables to input from `process.env` dictionary. * The `ProcessEnv` interface represents the imported environment variables to input from `process.env` dictionary.
*/ */
export interface ProcessEnv { export interface ProcessEnv {
/** /**
* `APP_HTTP_LISTEN_HOST` is the host for the HTTP web app. * `APP_HTTP_LISTEN_HOST` is the host for the HTTP web app.
*/ */
APP_HTTP_HOST?: string; APP_HTTP_HOST?: string;
/** /**
* `APP_HTTP_LISTEN_PORT` is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy). * `APP_HTTP_LISTEN_PORT` is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy).
*/ */
APP_HTTP_PORT?: string; APP_HTTP_PORT?: string;
/** /**
* `APP_URL` is the URL used to access the Node application (usually by a reverse proxy). * `APP_URL` is the URL used to access the Node application (usually by a reverse proxy).
*/ */
APP_HTTP_URL?: string; APP_HTTP_URL?: string;
/** /**
* `SITE_NAME` is used for page generation. * `SITE_NAME` is used for page generation.
*/ */
SITE_NAME?: string; SITE_NAME?: string;
/** /**
* `SITE_HOST` is used for generating links for the search index. (If you leave this blank it should work using relative paths.) * `SITE_HOST` is used for generating links for the search index. (If you leave this blank it should work using relative paths.)
*/ */
SITE_WELCOME_MESSAGE?: string; SITE_WELCOME_MESSAGE?: string;
/** /**
* `SITE_URL` is used for generating links for the search index. (If you leave this blank it should work using relative paths.) * `SITE_URL` is used for generating links for the search index. (If you leave this blank it should work using relative paths.)
*/ */
SITE_HOST?: string; SITE_HOST?: string;
/** /**
* `WELCOME_MESSAGE` is used for the homepage instead of `"Welcome to ${SITE_NAME}!"` * `WELCOME_MESSAGE` is used for the homepage instead of `"Welcome to ${SITE_NAME}!"`
*/ */
SITE_URL?: string; SITE_URL?: string;
/** /**
* `PUBLIC_PATH` is the relative path to the directory to this project root for the public files. * `PUBLIC_PATH` is the relative path to the directory to this project root for the public files.
*/ */
PUBLIC_PATH?: string; PUBLIC_PATH?: string;
/** /**
* `PAGES_PATH` is the relative path to the directory to this project root for the pages rather than public files. * `PAGES_PATH` is the relative path to the directory to this project root for the pages rather than public files.
*/ */
PAGES_PATH?: string; PAGES_PATH?: string;
/** /**
* `ASSETS_PATH` is the relative path to the directory to this project root for the static asset files. * `ASSETS_PATH` is the relative path to the directory to this project root for the static asset files.
*/ */
ASSETS_PATH?: string; ASSETS_PATH?: string;
/** /**
* `SOLR_DOCS_HOST` is the host for Apache Solr's core for indexed documents. * `SOLR_DOCS_HOST` is the host for Apache Solr's core for indexed documents.
*/ */
SOLR_DOCS_HOST?: string; SOLR_DOCS_HOST?: string;
/** /**
* `SOLR_DOCS_PORT` is the port for Apache Solr's core for indexed documents. * `SOLR_DOCS_PORT` is the port for Apache Solr's core for indexed documents.
*/ */
SOLR_DOCS_PORT?: string; SOLR_DOCS_PORT?: string;
/** /**
* `SOLR_DOCS_CORE` is the core name for Apache Solr's core for indexed documents. * `SOLR_DOCS_CORE` is the core name for Apache Solr's core for indexed documents.
*/ */
SOLR_DOCS_CORE?: string; SOLR_DOCS_CORE?: string;
/** /**
* `SOLR_DOCS_URL` is the URL to access Apache Solr's core for indexed documents. It is used by Gulp and the Search feature. * `SOLR_DOCS_URL` is the URL to access Apache Solr's core for indexed documents. It is used by Gulp and the Search feature.
*/ */
SOLR_DOCS_URL?: string; SOLR_DOCS_URL?: string;
/** /**
* `SOLR_LAW_HOST` is the host for Apache Solr's core for indexed laws. * `SOLR_LAW_HOST` is the host for Apache Solr's core for indexed laws.
*/ */
SOLR_LAW_HOST?: string; SOLR_LAW_HOST?: string;
/** /**
* `SOLR_LAW_PORT` is the port for Apache Solr's core for indexed laws. * `SOLR_LAW_PORT` is the port for Apache Solr's core for indexed laws.
*/ */
SOLR_LAW_PORT?: string; SOLR_LAW_PORT?: string;
/** /**
* `SOLR_LAW_CORE` is the core name for Apache Solr's core for indexed laws. * `SOLR_LAW_CORE` is the core name for Apache Solr's core for indexed laws.
*/ */
SOLR_LAW_CORE?: string; SOLR_LAW_CORE?: string;
/** /**
* `SOLR_LAW_URL` is the URL to access Apache Solr's core for indexed laws. It is used by Gulp and the Search feature. * `SOLR_LAW_URL` is the URL to access Apache Solr's core for indexed laws. It is used by Gulp and the Search feature.
*/ */
SOLR_LAW_URL?: string; SOLR_LAW_URL?: string;
/** /**
* `TIKA_HOST` is the host to access the Apache Tika app. * `TIKA_HOST` is the host to access the Apache Tika app.
*/ */
TIKA_HOST?: string; TIKA_HOST?: string;
/** /**
* `TIKA_PORT` is the port to access the Apache Tika app. * `TIKA_PORT` is the port to access the Apache Tika app.
*/ */
TIKA_PORT?: string; TIKA_PORT?: string;
/** /**
* `TIKA_URL` is the URL to access the Apache Tika app. * `TIKA_URL` is the URL to access the Apache Tika app.
*/ */
TIKA_URL?: string; TIKA_URL?: string;
} }
/** /**
* The `Config` interface represents the imported environment variables after imported from `process.env` dictionary. * The `Config` interface represents the imported environment variables after imported from `process.env` dictionary.
*/ */
export interface Config { export interface Config {
/** /**
* `appHttpHost` is the host for the HTTP web app. * `appHttpHost` is the host for the HTTP web app.
*/ */
appHttpHost: string; appHttpHost: string;
/** /**
* `appHttpPort` is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy). * `appHttpPort` is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy).
*/ */
appHttpPort: number|string; appHttpPort: number|string;
/** /**
* `appHttpUrl` is the URL used to access the Node application (usually by a reverse proxy). * `appHttpUrl` is the URL used to access the Node application (usually by a reverse proxy).
*/ */
appHttpUrl: string; appHttpUrl: string;
/** /**
* `siteName` is used for page generation. * `siteName` is used for page generation.
*/ */
siteName: string; siteName: string;
/** /**
* `siteWelcomeMessage` is used for the homepage instead of `"Welcome to ${process.env['SITE_NAME']}!"` * `siteWelcomeMessage` is used for the homepage instead of `"Welcome to ${process.env['SITE_NAME']}!"`
*/ */
siteWelcomeMessage: string; siteWelcomeMessage: string;
/** /**
* `siteHost` is used for generating links for the search index. (If you leave this blank it should work using relative paths.) * `siteHost` is used for generating links for the search index. (If you leave this blank it should work using relative paths.)
*/ */
siteHost: string; siteHost: string;
/** /**
* `siteUrl` is used for generating links for the search index. (If you leave this blank it should work using relative paths.) * `siteUrl` is used for generating links for the search index. (If you leave this blank it should work using relative paths.)
*/ */
siteUrl: string; siteUrl: string;
/** /**
* `publicPath` is the relative path to the directory to this project root for the public files. * `publicPath` is the relative path to the directory to this project root for the public files.
*/ */
publicPath: string; publicPath: string;
/** /**
* `pagesPath` is the relative path to the directory to this project root for the pages rather than public files. * `pagesPath` is the relative path to the directory to this project root for the pages rather than public files.
*/ */
pagesPath: string; pagesPath: string;
/** /**
* `assetsPath` is the relative path to the directory to this project root for the static asset files. * `assetsPath` is the relative path to the directory to this project root for the static asset files.
*/ */
assetsPath: string; assetsPath: string;
/** /**
* `viewsPath' is the relative path to the directory to this project root for the view templates. * `viewsPath' is the relative path to the directory to this project root for the view templates.
*/ */
viewsPath: string; viewsPath: string;
/** /**
* `solrDocsHost` is the host for Apache Solr's core for indexed documents. * `solrDocsHost` is the host for Apache Solr's core for indexed documents.
*/ */
solrDocsHost: string; solrDocsHost: string;
/** /**
* `solrDocsPort` is the port for Apache Solr's core for indexed documents. * `solrDocsPort` is the port for Apache Solr's core for indexed documents.
*/ */
solrDocsPort: number|string; solrDocsPort: number|string;
/** /**
* `solrDocsCore` is the core name for Apache Solr's core for indexed documents. * `solrDocsCore` is the core name for Apache Solr's core for indexed documents.
*/ */
solrDocsCore: string; solrDocsCore: string;
/** /**
* `solrDocsUrl` is the URL to access Apache Solr's core for indexed documents. It is used by Gulp and the Search feature. * `solrDocsUrl` is the URL to access Apache Solr's core for indexed documents. It is used by Gulp and the Search feature.
*/ */
solrDocsUrl: string; solrDocsUrl: string;
/** /**
* `solrLawHost` is the host for Apache Solr's core for indexed laws. * `solrLawHost` is the host for Apache Solr's core for indexed laws.
*/ */
solrLawHost: string; solrLawHost: string;
/** /**
* `solrLawPort` is the port for Apache Solr's core for indexed laws. * `solrLawPort` is the port for Apache Solr's core for indexed laws.
*/ */
solrLawPort: number|string; solrLawPort: number|string;
/** /**
* `solrLawCore` is the core name for Apache Solr's core for indexed laws. * `solrLawCore` is the core name for Apache Solr's core for indexed laws.
*/ */
solrLawCore: string; solrLawCore: string;
/** /**
* `solrLawUrl` is the URL to access Apache Solr's core for indexed laws. It is used by Gulp and the Search feature. * `solrLawUrl` is the URL to access Apache Solr's core for indexed laws. It is used by Gulp and the Search feature.
*/ */
solrLawUrl: string; solrLawUrl: string;
/** /**
* `tikaHost` is the host to access the Apache Tika app. * `tikaHost` is the host to access the Apache Tika app.
*/ */
tikaHost: string; tikaHost: string;
/** /**
* `tikaPort` is the port to access the Apache Tika app. * `tikaPort` is the port to access the Apache Tika app.
*/ */
tikaPort: string|number; tikaPort: string|number;
/** /**
* `tikaUrl` is the URL to access the Apache Tika app. * `tikaUrl` is the URL to access the Apache Tika app.
*/ */
tikaUrl: string; tikaUrl: string;
} }
console.log(`Configuring .env and expanding .env to include environment variable references.`);
import path from 'path'; import path from 'path';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand'; import dotenvExpand from 'dotenv-expand';
@ -195,20 +193,20 @@ const __dirname = path.dirname(__filename);
const env: ProcessEnv = {}; const env: ProcessEnv = {};
let dotEnvConfig = dotenv.config({ let dotEnvConfig = dotenv.config({
path: path.join(__dirname, '.env'), path: path.join(__dirname, '.env'),
processEnv: dotenv.config({ processEnv: dotenv.config({
path: path.join(__dirname, '..', '..', '.env'), path: path.join(__dirname, '..', '..', '.env'),
processEnv: env as dotenv.DotenvPopulateInput}) as dotenv.DotenvPopulateInput processEnv: env as dotenv.DotenvPopulateInput}) as dotenv.DotenvPopulateInput
}); });
dotEnvConfig = dotenvExpand.expand({ dotEnvConfig = dotenvExpand.expand({
parsed: env as dotenvExpand.DotenvParseInput, parsed: env as dotenvExpand.DotenvParseInput,
processEnv: process.env as dotenvExpand.DotenvParseInput processEnv: process.env as dotenvExpand.DotenvParseInput
}); });
export const getAppHttpHost = () => env.APP_HTTP_HOST||'nm3clol-express-app'; export const getAppHttpHost = () => env.APP_HTTP_HOST||'nm3clol-express-app';
export const getAppHttpPort = () => process.env.PORT||env.APP_HTTP_PORT||3000; export const getAppHttpPort = () => process.env.PORT||env.APP_HTTP_PORT||3000;
export const getAppHttpUrl = () => { export const getAppHttpUrl = () => {
return env.APP_HTTP_URL || `http://${getAppHttpHost() + ((getAppHttpPort() == '80') ? '' : ':' + getAppHttpPort())}` return env.APP_HTTP_URL || `http://${getAppHttpHost() + ((getAppHttpPort() == '80') ? '' : ':' + getAppHttpPort())}`
}; };
export const getSiteName = () => env.SITE_NAME||"(dev) No Moss 3 Carbo Landfill Online Localhost"; export const getSiteName = () => env.SITE_NAME||"(dev) No Moss 3 Carbo Landfill Online Localhost";
@ -237,31 +235,31 @@ export const getTikaPort = () => parseInt(env.TIKA_PORT||'9998');
export const getTikaUrl = () => env.TIKA_URL||`http://${getTikaHost()}:${getTikaPort()}`; export const getTikaUrl = () => env.TIKA_URL||`http://${getTikaHost()}:${getTikaPort()}`;
export const config: Config = { export const config: Config = {
appHttpHost: getAppHttpHost(), appHttpHost: getAppHttpHost(),
appHttpPort: getAppHttpPort(), appHttpPort: getAppHttpPort(),
appHttpUrl: getAppHttpUrl(), appHttpUrl: getAppHttpUrl(),
siteName: getSiteName(), siteName: getSiteName(),
siteWelcomeMessage: getSiteWelcomeMessage(), siteWelcomeMessage: getSiteWelcomeMessage(),
siteHost: getSiteHost(), siteHost: getSiteHost(),
siteUrl: getSiteUrl(), siteUrl: getSiteUrl(),
publicPath: getPublicPath(), publicPath: getPublicPath(),
pagesPath: getPagesPath(), pagesPath: getPagesPath(),
assetsPath: getAssetsPath(), assetsPath: getAssetsPath(),
viewsPath: getViewsPath(), viewsPath: getViewsPath(),
solrDocsHost: getSolrDocsHost(), solrDocsHost: getSolrDocsHost(),
solrDocsPort: getSolrDocsPort(), solrDocsPort: getSolrDocsPort(),
solrDocsCore: getSolrDocsCore(), solrDocsCore: getSolrDocsCore(),
solrDocsUrl: getSolrDocsUrl(), solrDocsUrl: getSolrDocsUrl(),
solrLawHost: getSolrLawHost(), solrLawHost: getSolrLawHost(),
solrLawPort: getSolrLawPort(), solrLawPort: getSolrLawPort(),
solrLawCore: getSolrLawCore(), solrLawCore: getSolrLawCore(),
solrLawUrl: getSolrLawCore(), solrLawUrl: getSolrLawCore(),
tikaHost: getTikaHost(), tikaHost: getTikaHost(),
tikaPort: getTikaPort(), tikaPort: getTikaPort(),
tikaUrl: getTikaUrl(), tikaUrl: getTikaUrl(),
}; };

View File

@ -1,4 +1,4 @@
export interface Breadcrumb { export interface Breadcrumb {
title: string; title: string;
url: string; url: string;
} }

View File

@ -186,6 +186,14 @@ const renderArchive = (html: string, paths: string[]) => {
return html; return html;
} }
const isMsOfficeViewerSupported = (file: string) => {
return path.extname(file).search(/^((?:.pptx)|(?:.docx)|(?:.xlsx)|(?:.ppt)|(?:.doc)|(?:.xls))$/ig) != -1;
}
const isGoogleDocsViewerSupported = (file: string) => {
return isMsOfficeViewerSupported(file) || path.extname(file).search(/^((?:.pdf))$/ig) != -1;
}
export default { export default {
leftTrimFirstDirectory, leftTrimFirstDirectory,
trimSlashes, trimSlashes,
@ -205,4 +213,6 @@ export default {
inspect, inspect,
md, md,
moment, moment,
isMsOfficeViewerSupported,
isGoogleDocsViewerSupported,
}; };

View File

@ -5,4 +5,4 @@
import path from 'path'; import path from 'path';
export const normalize = (value: string) => path.posix.normalize(path.posix.join('/', value));; export const normalize = (value: string) => path.posix.normalize(path.posix.join('/', value));;
export default (value: string) => (value.charAt(0) === '!' ? `!${normalize(value.substr(1))}` : normalize(value)); export default (value: string) => (value.charAt(0) === '!' ? `!${normalize(value.substr(1))}` : normalize(value));

View File

@ -78,7 +78,7 @@ export default function () {
} }
}); });
const renderData = { breadcrumbs, content, filePath, fullFilePath, paths, req, route, ...fmData }; const renderData = { breadcrumbs, content, filePath, fullFilePath, paths, req, route, ...fmData };
res.render("page", { h: helpers, ...renderData }); res.render("page", { h: helpers, path, config, ...renderData });
}); });
}); });
@ -122,7 +122,7 @@ export default function () {
console.log(`Setting route for ${route}`); console.log(`Setting route for ${route}`);
pageRouter.get(route, async (req, res) => { pageRouter.get(route, async (req, res) => {
const html = fs.readFileSync(fullFilePath).toString(); const html = fs.readFileSync(fullFilePath).toString();
const renderData = { route, filePath, fullFilePath, req, paths, html }; const renderData = { route, filePath, fullFilePath, req, paths, html, path, config };
res.render("archive", { h: helpers, ...renderData }); res.render("archive", { h: helpers, ...renderData });
}); });
}); });
@ -183,7 +183,7 @@ export default function () {
} }
}); });
const renderData = { breadcrumbs, route, filePath, fullFilePath, req, paths, directory: path.join('public', directory), videoURL, subtitleURL, subtitleVTT, info }; const renderData = { breadcrumbs, route, filePath, fullFilePath, req, paths, directory: path.join('public', directory), videoURL, subtitleURL, subtitleVTT, info };
res.render("video-player", { h: helpers, ...renderData }); res.render("video-player", { h: helpers, path, config, ...renderData });
} }
}); });
}); });

View File

@ -81,15 +81,15 @@ export interface ServeHandlerOptions {
* current working directory. * current working directory.
* *
* For example, if serving a Jekyll app, it would look like this: * For example, if serving a Jekyll app, it would look like this:
* { * {
* "public": "_site" * "public": "_site"
* } * }
* *
* Using absolute path: * Using absolute path:
* *
* { * {
* "public": "/path/to/your/_site" * "public": "/path/to/your/_site"
* } * }
* *
* NOTE: The path cannot contain globs or regular expressions. * NOTE: The path cannot contain globs or regular expressions.
*/ */
@ -101,17 +101,17 @@ export interface ServeHandlerOptions {
* with status code 301 to the same path, but with the extension dropped. * with status code 301 to the same path, but with the extension dropped.
* *
* You can disable the feature like follows: * You can disable the feature like follows:
* { * {
* "cleanUrls": false * "cleanUrls": false
* } * }
* *
* However, you can also restrict it to certain paths: * However, you can also restrict it to certain paths:
* { * {
* "cleanUrls": [ * "cleanUrls": [
* "/app/**", * "/app/**",
* "/!components/**" * "/!components/**"
* ] * ]
* } * }
* *
* NOTE: The paths can only contain globs that are matched using minimatch. * NOTE: The paths can only contain globs that are matched using minimatch.
*/ */
@ -121,19 +121,19 @@ export interface ServeHandlerOptions {
* different one behind the curtains, this option is what you need. * different one behind the curtains, this option is what you need.
* *
* It's perfect for single page applications (SPAs), for example: * It's perfect for single page applications (SPAs), for example:
* { * {
* "rewrites": [ * "rewrites": [
* { "source": "app/**", "destination": "/index.html" }, * { "source": "app/**", "destination": "/index.html" },
* { "source": "projects/edit", "destination": "/edit-project.html" } * { "source": "projects/edit", "destination": "/edit-project.html" }
* ] * ]
* } * }
* *
* You can also use so-called "routing segments" as follows: * You can also use so-called "routing segments" as follows:
* { * {
* "rewrites": [ * "rewrites": [
* { "source": "/projects/:id/edit", "destination": "/edit-project-:id.html" }, * { "source": "/projects/:id/edit", "destination": "/edit-project-:id.html" },
* ] * ]
* } * }
* *
* Now, if a visitor accesses /projects/123/edit, it will respond with the file /edit-project-123.html. * Now, if a visitor accesses /projects/123/edit, it will respond with the file /edit-project-123.html.
* *
@ -184,8 +184,9 @@ export const directoryTemplate = (vals: ServeDirectoryTemplateParameters) => {
}); });
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ejs.renderFile(path.join(config.viewsPath, 'directory.ejs'), { breadcrumbs, h: helpers, ...vals }, (err, str) => { ejs.renderFile(path.join(config.viewsPath, 'directory.ejs'), { h: helpers, path, config, breadcrumbs, ...vals }, (err, str) => {
if (err) { if (err) {
console.error(err);
reject(err); reject(err);
} else { } else {
resolve(str); resolve(str);
@ -196,8 +197,9 @@ export const directoryTemplate = (vals: ServeDirectoryTemplateParameters) => {
export const errorTemplate = (vals: ServeErrorTemplateParameters) => { export const errorTemplate = (vals: ServeErrorTemplateParameters) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ejs.renderFile(path.join(config.viewsPath, 'error.ejs'), { h: helpers, ...vals }, (err, str) => { ejs.renderFile(path.join(config.viewsPath, 'error.ejs'), { h: helpers, path, config, ...vals }, (err, str) => {
if (err) { if (err) {
console.error(err);
reject(err); reject(err);
} else { } else {
resolve(str); resolve(str);
@ -1012,4 +1014,4 @@ export default async (request: Request, response: ServerResponse, serveConfig: S
response.writeHead(response.statusCode || 200, headers); response.writeHead(response.statusCode || 200, headers);
stream.pipe(response); stream.pipe(response);
}; };

View File

@ -2,24 +2,24 @@
* Needed until the conversion is completed. * Needed until the conversion is completed.
*/ */
export interface IncorrectStyleSolrDocument { export interface IncorrectStyleSolrDocument {
id: string; id: string;
sha256sum: string[]; sha256sum: string[];
url: string[]; url: string[];
content_length: number[]; content_length: number[];
content_type: string[]; content_type: string[];
text: string[]; text: string[];
_version_?: number; _version_?: number;
} }
/** /**
* Describes Solr full-text search properties for a document file in the public repository. * Describes Solr full-text search properties for a document file in the public repository.
*/ */
export interface SolrDocument { export interface SolrDocument {
id: string; id: string;
sha256sum: string; sha256sum: string;
url: string; url: string;
content_length: number; content_length: number;
content_type: string; content_type: string;
text: string; text: string;
_version_?: number; _version_?: number;
} }

View File

@ -38,5 +38,5 @@ app.listen(config.appHttpPort, () => {
console.log(`To access your app, you can use the 127.0.0.1 host, http://127.0.0.1:${config.appHttpPort}.`); console.log(`To access your app, you can use the 127.0.0.1 host, http://127.0.0.1:${config.appHttpPort}.`);
console.log(`To access your app, you can use the ::1 host, http://[::1]:${config.appHttpPort}.`); console.log(`To access your app, you can use the ::1 host, http://[::1]:${config.appHttpPort}.`);
console.log(`To access your app, you might can use the app host name, ${config.appHttpUrl}.`); console.log(`To access your app, you might can use the app host name, ${config.appHttpUrl}.`);
console.log(`This app is configured to use the web site URL, ${config.siteUrl}.`); console.log(`This app is configured to use the web site URL for URL generation, as needed, ${config.siteUrl}. Certain site features won't work correctly unless this is on a publicly accessible URL.`);
}); });

View File

@ -1,11 +1,11 @@
import { ReadStream } from 'fs' import { ReadStream } from 'fs'
import fetch from 'node-fetch' import fetch from 'node-fetch'
let join = (...args: String[]) => { let join = (...args: String[]) => {
let output = ""; let output = "";
args.forEach((arg) => { args.forEach((arg) => {
output += arg; output += arg;
}) })
return output; return output;
}; };
import { ContentResource, MetadataResource } from './types.mjs' import { ContentResource, MetadataResource } from './types.mjs'
import { Writable } from 'stream'; import { Writable } from 'stream';

View File

@ -1,81 +1,81 @@
export interface MetadataResource { export interface MetadataResource {
'pdf:unmappedUnicodeCharsPerPage': string[] 'pdf:unmappedUnicodeCharsPerPage': string[]
'pdf:PDFVersion': string 'pdf:PDFVersion': string
'xmp:CreatorTool': string 'xmp:CreatorTool': string
'pdf:hasXFA': string 'pdf:hasXFA': string
'access_permission:modify_annotations': string 'access_permission:modify_annotations': string
'access_permission:can_print_degraded': string 'access_permission:can_print_degraded': string
'X-TIKA:Parsed-By-Full-Set': string[] 'X-TIKA:Parsed-By-Full-Set': string[]
'pdf:num3DAnnotations': string 'pdf:num3DAnnotations': string
'dcterms:created': string 'dcterms:created': string
'language': string 'language': string
'dcterms:modified': string 'dcterms:modified': string
'dc:format': string 'dc:format': string
'pdf:docinfo:creator_tool': string 'pdf:docinfo:creator_tool': string
'pdf:overallPercentageUnmappedUnicodeChars': string 'pdf:overallPercentageUnmappedUnicodeChars': string
'access_permission:fill_in_form': string 'access_permission:fill_in_form': string
'pdf:docinfo:modified': string 'pdf:docinfo:modified': string
'pdf:hasCollection': string 'pdf:hasCollection': string
'pdf:encrypted': string 'pdf:encrypted': string
'pdf:containsNonEmbeddedFont': string 'pdf:containsNonEmbeddedFont': string
'Content-Length': string 'Content-Length': string
'pdf:hasMarkedContent': string 'pdf:hasMarkedContent': string
'Content-Type': string 'Content-Type': string
'pdf:producer': string 'pdf:producer': string
'pdf:totalUnmappedUnicodeChars': string 'pdf:totalUnmappedUnicodeChars': string
'access_permission:extract_for_accessibility': string 'access_permission:extract_for_accessibility': string
'access_permission:assemble_document': string 'access_permission:assemble_document': string
'xmpTPg:NPages': string 'xmpTPg:NPages': string
'pdf:hasXMP': string 'pdf:hasXMP': string
'pdf:charsPerPage': string[] 'pdf:charsPerPage': string[]
'access_permission:extract_content': string 'access_permission:extract_content': string
'access_permission:can_print': string 'access_permission:can_print': string
'X-TIKA:Parsed-By': string[] 'X-TIKA:Parsed-By': string[]
'pdf:annotationTypes': string 'pdf:annotationTypes': string
'access_permission:can_modify': string 'access_permission:can_modify': string
'pdf:docinfo:producer': string 'pdf:docinfo:producer': string
'pdf:docinfo:created': string 'pdf:docinfo:created': string
'pdf:annotationSubtypes': string 'pdf:annotationSubtypes': string
'pdf:containsDamagedFont': string 'pdf:containsDamagedFont': string
} }
export interface ContentResource { export interface ContentResource {
'pdf:unmappedUnicodeCharsPerPage': string[] 'pdf:unmappedUnicodeCharsPerPage': string[]
'pdf:PDFVersion': string 'pdf:PDFVersion': string
'xmp:CreatorTool': string 'xmp:CreatorTool': string
'pdf:hasXFA': string 'pdf:hasXFA': string
'access_permission:modify_annotations': string 'access_permission:modify_annotations': string
'access_permission:can_print_degraded': string 'access_permission:can_print_degraded': string
'X-TIKA:Parsed-By-Full-Set': string[] 'X-TIKA:Parsed-By-Full-Set': string[]
'pdf:num3DAnnotations': string 'pdf:num3DAnnotations': string
'dcterms:created': string 'dcterms:created': string
'dcterms:modified': string 'dcterms:modified': string
'dc:format': string 'dc:format': string
'pdf:docinfo:creator_tool': string 'pdf:docinfo:creator_tool': string
'pdf:overallPercentageUnmappedUnicodeChars': string 'pdf:overallPercentageUnmappedUnicodeChars': string
'access_permission:fill_in_form': string 'access_permission:fill_in_form': string
'pdf:docinfo:modified': string 'pdf:docinfo:modified': string
'pdf:hasCollection': string 'pdf:hasCollection': string
'pdf:encrypted': string 'pdf:encrypted': string
'pdf:containsNonEmbeddedFont': string 'pdf:containsNonEmbeddedFont': string
'Content-Length': string 'Content-Length': string
'pdf:hasMarkedContent': string 'pdf:hasMarkedContent': string
'Content-Type': string 'Content-Type': string
'pdf:producer': string 'pdf:producer': string
'pdf:totalUnmappedUnicodeChars': string 'pdf:totalUnmappedUnicodeChars': string
'access_permission:extract_for_accessibility': string 'access_permission:extract_for_accessibility': string
'access_permission:assemble_document': string 'access_permission:assemble_document': string
'xmpTPg:NPages': string 'xmpTPg:NPages': string
'pdf:hasXMP': string 'pdf:hasXMP': string
'pdf:charsPerPage': string[] 'pdf:charsPerPage': string[]
'access_permission:extract_content': string 'access_permission:extract_content': string
'access_permission:can_print': string 'access_permission:can_print': string
'X-TIKA:Parsed-By': string[] 'X-TIKA:Parsed-By': string[]
'X-TIKA:content': string 'X-TIKA:content': string
'pdf:annotationTypes': string 'pdf:annotationTypes': string
'access_permission:can_modify': string 'access_permission:can_modify': string
'pdf:docinfo:producer': string 'pdf:docinfo:producer': string
'pdf:docinfo:created': string 'pdf:docinfo:created': string
'pdf:annotationSubtypes': string 'pdf:annotationSubtypes': string
'pdf:containsDamagedFont': string 'pdf:containsDamagedFont': string
} }

View File

@ -4,18 +4,13 @@
<title><%=h.getDirectoryTitle(directory)%></title> <title><%=h.getDirectoryTitle(directory)%></title>
<%- include('./includes/common-head.ejs') %> <%- include('./includes/common-head.ejs') %>
</head> </head>
<body onload="initPage()"> <body onload="initPage()">
<%- include('./includes/top-navbar.ejs') %> <%- include('./includes/top-navbar.ejs') %>
<%- include('./includes/no-trash-svg.ejs') %> <%- include('./includes/no-trash-svg.ejs') %>
<main class="container"> <main class="container">
<header> <header>
<%- include('./includes/breadcrumbs.ejs') %> <%- include('./includes/breadcrumbs.ejs') %>
</header> </header>
<% if (h.directoryContainsReadme(directory)) {%> <% if (h.directoryContainsReadme(directory)) {%>
<div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg"> <div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg">
<div class="col-lg-12 p-3 p-lg-5 pt-lg-3"> <div class="col-lg-12 p-3 p-lg-5 pt-lg-3">
@ -27,13 +22,69 @@
<b>Document Date:</b> <%= h.moment(h.readmeFm(directory).docDate).format('MMMM D, YYYY') %> <b>Document Date:</b> <%= h.moment(h.readmeFm(directory).docDate).format('MMMM D, YYYY') %>
<% } %> <% } %>
<%if (typeof h.readmeFm(directory).file !== 'undefined') { %> <%if (typeof h.readmeFm(directory).file !== 'undefined') { %>
<b>Attached Document:</b> <a href="<%- encodeURI(h.readmeFm(directory).file) %>"><%- h.readmeFm(directory).file %></a> <b>Document:</b> <a href="<%- encodeURI(path.basename(h.readmeFm(directory).file)) %>"><%- h.readmeFm(directory).file %></a>
<% } %> <% } %>
</small> </small>
</p> </p>
<% } %> <% } %>
<% if (typeof h.readmeFm(directory).file !== 'undefined') { %> <% if (typeof h.readmeFm(directory).file !== 'undefined') { %>
<iframe src="<%- encodeURI(h.readmeFm(directory).file) %>" style="width: 100%; height: 90vh;"></iframe> <ul class="nav nav-tabs" id="readerTab" role="tablist">
<% if (h.isMsOfficeViewerSupported(h.readmeFm(directory).file)) { %>
<li class="nav-item" role="presentation">
<button class="nav-link active" id="ms-office-viewer-tab" data-bs-toggle="tab" data-bs-target="#ms-office-viewer" type="button" role="tab" aria-controls="ms-office-viewer" aria-selected="true">Microsoft Office Web Viewer</button>
</li>
<% } %>
<% if (h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) { %>
<li class="nav-item" role="presentation">
<button class="nav-link<%=h.isMsOfficeViewerSupported(h.readmeFm(directory).file) ? '' : ' active'%>" id="google-docs-viewer-tab" data-bs-toggle="tab" data-bs-target="#google-docs-viewer" type="button" role="tab" aria-controls="google-docs-viewer" aria-selected="false">Google Docs Viewer</button>
</li>
<% } %>
<li class="nav-item" role="presentation">
<button class="nav-link" id="view-download-tab" data-bs-toggle="tab" data-bs-target="#view-download" type="button" role="tab" aria-controls="view-download" aria-selected="false">View/Download</button>
</li>
</ul>
<div class="tab-content">
<% if (h.isMsOfficeViewerSupported(h.readmeFm(directory).file)) { %>
<div class="tab-pane active" id="ms-office-viewer" role="tabpanel" aria-labelledby="ms-office-viewer-tab" tabindex="0">
<iframe src="https://view.officeapps.live.com/op/embed.aspx?src=<%- encodeURIComponent(h.trimSlashes(config.siteUrl) + path.posix.join(breadcrumbs[breadcrumbs.length-1].url, (h.readmeFm(directory).file.replaceAll('\\', '/')))) %>" style="width: 100%; height: 85vh; border: solid 1px #dfd7ca; border-top: 0px; border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem;" frameborder="0"></iframe>
</div>
<% } %>
<% if (h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) { %>
<div class="tab-pane<%=h.isMsOfficeViewerSupported(h.readmeFm(directory).file) ? '' : ' active'%>" id="google-docs-viewer" role="tabpanel" aria-labelledby="google-docs-viewer-tab" tabindex="0">
<iframe src="https://docs.google.com/gview?embedded=true&url=<%- encodeURIComponent(h.trimSlashes(config.siteUrl) + path.posix.join(breadcrumbs[breadcrumbs.length-1].url, (h.readmeFm(directory).file.replaceAll('\\', '/')))) %>" style="width: 100%; height: 85vh; border: solid 1px #dfd7ca; border-top: 0px; border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem;" frameborder="0"></iframe>
</div>
<% } %>
<div class="tab-pane<%=(h.isMsOfficeViewerSupported(h.readmeFm(directory).file) || h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) ? '' : ' active'%>" id="view-download" role="tabpanel" aria-labelledby="view-download-tab" tabindex="0">
<iframe src="about:blank" style="width: 100%; height: 85vh; border: solid 1px #dfd7ca; border-top: 0px; border-bottom-left-radius: 0.25rem; border-bottom-right-radius: 0.25rem;" frameborder="0"></iframe>
</div>
<script>
$(document).ready(function () {
var viewDownloadTabButton = $('button#view-download-tab');
var viewDownloadTab = new bootstrap.Tab(viewDownloadTabButton);
var viewDownloadTabPaneIFrame = $('#view-download iframe');
viewDownloadTabButton.on('click', function (event) {
event.preventDefault();
viewDownloadTab.show();
if (viewDownloadTabPaneIFrame.attr('src') == 'about:blank') {
console.log('View/Download tab clicked. Loading iframe.');
viewDownloadTabPaneIFrame.attr('src', '<%- encodeURI(h.readmeFm(directory).file) %>');
} else {
console.log('View/Download tab clicked. iframe previously loaded.');
}
})
})
</script>
<% if (!h.isMsOfficeViewerSupported(h.readmeFm(directory).file) && !h.isGoogleDocsViewerSupported(h.readmeFm(directory).file)) { %>
<script>
$(document).ready(function () {
setTimeout(function () {
var viewDownloadTabButton = $('button#view-download-tab');
viewDownloadTabButton.click();
}, 100);
});
</script>
<% } %>
</div>
<% } else { %> <% } else { %>
<%- h.printReadme(directory) %> <%- h.printReadme(directory) %>
<% } %> <% } %>
@ -54,7 +105,6 @@
</div> </div>
<% } %> <% } %>
<% } %> <% } %>
<ul id="files" class="list-group shadow-lg"> <ul id="files" class="list-group shadow-lg">
<% files.forEach(function(value, index) { %> <% files.forEach(function(value, index) { %>
<li class="list-group-item list-group-item-action flex-column align-items-start"> <li class="list-group-item list-group-item-action flex-column align-items-start">
@ -65,7 +115,6 @@
<% }); %> <% }); %>
</ul> </ul>
</main> </main>
<%- include('./includes/bottom-navbar.ejs') %> <%- include('./includes/bottom-navbar.ejs') %>
<%- include('./includes/bottom-scripts.ejs') %> <%- include('./includes/bottom-scripts.ejs') %>
</body> </body>

View File

@ -1,122 +1,105 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<style> <style>
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
cursor: default; cursor: default;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
main,
main, aside,
aside, section {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
main {
height: 100%;
}
aside {
background: #000;
flex-shrink: 1;
padding: 30px 20px;
}
aside p {
margin: 0;
color: #999999;
font-size: 14px;
line-height: 24px;
}
aside a {
color: #fff;
text-decoration: none;
}
section span {
font-size: 24px;
font-weight: 500;
display: block;
border-bottom: 1px solid #EAEAEA;
text-align: center;
padding-bottom: 20px;
width: 100px;
}
section p {
font-size: 14px;
font-weight: 400;
}
section span+p {
margin: 20px 0 0 0;
}
@media (min-width: 768px) {
section { section {
display: flex; height: 40px;
justify-content: center; flex-direction: row;
align-items: center;
flex-direction: column;
} }
section span,
main {
height: 100%;
}
aside {
background: #000;
flex-shrink: 1;
padding: 30px 20px;
}
aside p {
margin: 0;
color: #999999;
font-size: 14px;
line-height: 24px;
}
aside a {
color: #fff;
text-decoration: none;
}
section span {
font-size: 24px;
font-weight: 500;
display: block;
border-bottom: 1px solid #EAEAEA;
text-align: center;
padding-bottom: 20px;
width: 100px;
}
section p { section p {
font-size: 14px; height: 100%;
font-weight: 400; line-height: 40px;
}
section span {
border-bottom: 0;
border-right: 1px solid #EAEAEA;
padding: 0 20px 0 0;
width: auto;
} }
section span+p { section span+p {
margin: 20px 0 0 0; margin: 0;
padding-left: 20px;
} }
aside {
@media (min-width: 768px) { padding: 50px 0;
section {
height: 40px;
flex-direction: row;
}
section span,
section p {
height: 100%;
line-height: 40px;
}
section span {
border-bottom: 0;
border-right: 1px solid #EAEAEA;
padding: 0 20px 0 0;
width: auto;
}
section span+p {
margin: 0;
padding-left: 20px;
}
aside {
padding: 50px 0;
}
aside p {
max-width: 520px;
text-align: center;
}
} }
aside p {
max-width: 520px;
text-align: center;
}
}
</style> </style>
</head> </head>
<body>
<body>
<main> <main>
<section> <section>
<span><% if (typeof statusCode !== 'undefined') { %><%= statusCode %><% } %></span> <span><% if (typeof statusCode !== 'undefined') { %><%= statusCode %><% } %></span>
<p><% if (typeof message !== 'undefined') { %><%= message %><% } %></p> <p><% if (typeof message !== 'undefined') { %><%= message %><% } %></p>
</section> </section>
</main> </main>
</body> </body>
</html>
</html>

View File

@ -9,4 +9,4 @@
Your use of this search feature constitutes your agreement with the <a style="color: #eee; font-weight: 900; text-transform: uppercase;" href="/search-policy">Search Policy</a>. Your use of this search feature constitutes your agreement with the <a style="color: #eee; font-weight: 900; text-transform: uppercase;" href="/search-policy">Search Policy</a>.
</p> </p>
</div> </div>
</nav> </nav>

View File

@ -1,6 +1,5 @@
<!-- Bootstrap JS (optional, if you need Bootstrap JS features) --> <!-- Bootstrap JS (optional, if you need Bootstrap JS features) -->
<script src="https://daball.me/vendor/jquery/jquery.min.js"></script>
<script src="https://daball.me/vendor/popper.js/dist/popper.min.js"></script> <script src="https://daball.me/vendor/popper.js/dist/popper.min.js"></script>
<script src="https://daball.me/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="https://daball.me/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="https://daball.me/vendor/jquery-easing/jquery.easing.min.js"></script> <script src="https://daball.me/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="https://daball.me/layouts/blog/js/blog.min.js"></script> <script src="https://daball.me/layouts/blog/js/blog.min.js"></script>

View File

@ -9,4 +9,4 @@
<a href="<%=breadcrumb.url%>"><%=breadcrumb.title%></a> <a href="<%=breadcrumb.url%>"><%=breadcrumb.title%></a>
<% } %> <% } %>
<% }) %> <% }) %>
</h1> </h1>

View File

@ -10,7 +10,6 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8937572456576531" crossorigin="anonymous"></script> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8937572456576531" crossorigin="anonymous"></script>
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link href="https://daball.me/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://daball.me/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Saira+Extra+Condensed:100,200,300,400,500,600,700,800,900" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Saira+Extra+Condensed:100,200,300,400,500,600,700,800,900" rel="stylesheet" />
@ -26,3 +25,4 @@
<link href="https://daball.me/vendor/simple-line-icons/css/simple-line-icons.css" rel="stylesheet" /> <link href="https://daball.me/vendor/simple-line-icons/css/simple-line-icons.css" rel="stylesheet" />
<link href="https://daball.me/layouts/blog/css/blog.min.css" rel="stylesheet" /> <link href="https://daball.me/layouts/blog/css/blog.min.css" rel="stylesheet" />
<link href="/css/nm3clol.css" rel="stylesheet" /> <link href="/css/nm3clol.css" rel="stylesheet" />
<script src="https://daball.me/vendor/jquery/jquery.min.js"></script>

View File

@ -1,18 +1,18 @@
<img id="no-trash-svg" alt="" src="/svg/no-trash.svg" class="no-trash-svg" onload="" /> <img id="no-trash-svg" alt="" src="/svg/no-trash.svg" class="no-trash-svg" onload="" />
<script> <script>
function is_iOS() { function is_iOS() {
return [ return [
'iPad Simulator', 'iPad Simulator',
'iPhone Simulator', 'iPhone Simulator',
'iPod Simulator', 'iPod Simulator',
'iPad', 'iPad',
'iPhone', 'iPhone',
'iPod' 'iPod'
].includes(navigator.platform) ].includes(navigator.platform)
// iPad on iOS 13 detection // iPad on iOS 13 detection
|| (navigator.userAgent.includes("Mac") && "ontouchend" in document) || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
} }
if (is_iOS() && navigator.userAgent.indexOf('AppleWebKit')) { if (is_iOS() && navigator.userAgent.indexOf('AppleWebKit')) {
document.getElementById("no-trash-svg").style.visibility = "hidden"; document.getElementById("no-trash-svg").style.visibility = "hidden";
} }
</script> </script>

View File

@ -13,4 +13,4 @@
</form> </form>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,18 +4,13 @@
<title><%= (typeof fm.title !== 'undefined') ? `${fm.title} - ${h.getSiteName()}` : h.getSiteName() %></title> <title><%= (typeof fm.title !== 'undefined') ? `${fm.title} - ${h.getSiteName()}` : h.getSiteName() %></title>
<%- include('./includes/common-head.ejs') %> <%- include('./includes/common-head.ejs') %>
</head> </head>
<body onload="initPage()"> <body onload="initPage()">
<%- include('./includes/top-navbar.ejs') %> <%- include('./includes/top-navbar.ejs') %>
<%- include('./includes/no-trash-svg.ejs') %> <%- include('./includes/no-trash-svg.ejs') %>
<main class="container"> <main class="container">
<header> <header>
<%- include('./includes/breadcrumbs.ejs') %> <%- include('./includes/breadcrumbs.ejs') %>
</header> </header>
<% if (typeof content !== 'undefined') {%> <% if (typeof content !== 'undefined') {%>
<div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg"> <div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg">
<div class="col-lg-12 p-3 p-lg-5 pt-lg-3"> <div class="col-lg-12 p-3 p-lg-5 pt-lg-3">
@ -29,7 +24,6 @@
</div> </div>
</div> </div>
<% } %> <% } %>
<ul id="files" class="list-group shadow-lg"> <ul id="files" class="list-group shadow-lg">
<% if (typeof files !== 'undefined') files.forEach(function(value, index) { %> <% if (typeof files !== 'undefined') files.forEach(function(value, index) { %>
<li class="list-group-item list-group-item-action flex-column align-items-start"> <li class="list-group-item list-group-item-action flex-column align-items-start">
@ -40,7 +34,6 @@
<% }) %> <% }) %>
</ul> </ul>
</main> </main>
<%- include('./includes/bottom-navbar.ejs') %> <%- include('./includes/bottom-navbar.ejs') %>
<%- include('./includes/bottom-scripts.ejs') %> <%- include('./includes/bottom-scripts.ejs') %>
</body> </body>

View File

@ -1,35 +1,34 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Search Error for <%- query %> - <%- h.getSiteName() %></title> <title>Search Error for <%- query %> - <%- h.getSiteName() %></title>
<%- include('./includes/common-head.ejs') %> <%- include('./includes/common-head.ejs') %>
</head> </head>
<body> <body>
<%- include('./includes/top-navbar.ejs') %> <%- include('./includes/top-navbar.ejs') %>
<%- include('./includes/no-trash-svg.ejs') %> <%- include('./includes/no-trash-svg.ejs') %>
<main class="container"> <main class="container">
<header> <header>
<%- include('./includes/breadcrumbs.ejs') %> <%- include('./includes/breadcrumbs.ejs') %>
</header> </header>
<div class="mt-3 p-3"> <div class="mt-3 p-3">
<p> <p>
Disclaimer: Use of the search feature is subject to both the <a href="/search-policy">Search Disclaimer: Use of the search feature is subject to both the <a href="/search-policy">Search
Policy</a> and the <a href="/privacy-policy">Privacy Policy</a>. Policy</a> and the <a href="/privacy-policy">Privacy Policy</a>.
</p> </p>
</div> </div>
<div id="searchError" class="mt-3 shadow-lg p-lg-5"> <div id="searchError" class="mt-3 shadow-lg p-lg-5">
<% if (typeof error !== 'undefined') {%> <% if (typeof error !== 'undefined') {%>
<p>An error occurred while attempting to perform a search.</p> <p>An error occurred while attempting to perform a search.</p>
<% if (typeof query !== 'undefined') {%><p><b>Search Query:</b> <span id="search-query"><%= query %></span></p><% } %> <% if (typeof query !== 'undefined') {%><p><b>Search Query:</b> <span id="search-query"><%= query %></span></p><% } %>
<% if (typeof error.code !== 'undefined') {%><p><b>Error Code:</b> <span id="error-code"><%= error.code %></span></p><% } %> <% if (typeof error.code !== 'undefined') {%><p><b>Error Code:</b> <span id="error-code"><%= error.code %></span></p><% } %>
<% if (typeof error.message !== 'undefined') {%><p><b>Error Message:</b> <span id="error-message"><%= error.message %></span></p><% } %> <% if (typeof error.message !== 'undefined') {%><p><b>Error Message:</b> <span id="error-message"><%= error.message %></span></p><% } %>
<% if (typeof error.innerError !== 'undefined' && typeof error.innerError.error !== 'undefined') { %> <% if (typeof error.innerError !== 'undefined' && typeof error.innerError.error !== 'undefined') { %>
<% if (typeof error.innerError.error.msg !== 'undefined') {%><p><b>Inner Error Message:</b> <span id="inner-error-message"><%- error.innerError.error.msg.replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\n", '<br>') %></span></p><% } %> <% if (typeof error.innerError.error.msg !== 'undefined') {%><p><b>Inner Error Message:</b> <span id="inner-error-message"><%- error.innerError.error.msg.replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\n", '<br>') %></span></p><% } %>
<% } %>
<% } %> <% } %>
<% } %> </div>
</div> <%- include('./includes/bottom-navbar.ejs') %>
<%- include('./includes/bottom-scripts.ejs') %>
<%- include('./includes/bottom-navbar.ejs') %> </body>
<%- include('./includes/bottom-scripts.ejs') %> </html>
</body>
</html>

View File

@ -1,73 +1,72 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Search Results for <%- query %> - <%- h.getSiteName() %></title> <title>Search Results for <%- query %> - <%- h.getSiteName() %></title>
<%- include('./includes/common-head.ejs') %> <%- include('./includes/common-head.ejs') %>
</head> </head>
<body> <body>
<%- include('./includes/top-navbar.ejs') %> <%- include('./includes/top-navbar.ejs') %>
<%- include('./includes/no-trash-svg.ejs') %> <%- include('./includes/no-trash-svg.ejs') %>
<main class="container"> <main class="container">
<header> <header>
<%- include('./includes/breadcrumbs.ejs') %> <%- include('./includes/breadcrumbs.ejs') %>
</header> </header>
<div class="container"> <div class="container">
<p> <p>
Disclaimer: Use of the search feature is subject to both the <a href="/search-policy">Search Disclaimer: Use of the search feature is subject to both the <a href="/search-policy">Search
Policy</a> and the <a href="/privacy-policy">Privacy Policy</a>. Policy</a> and the <a href="/privacy-policy">Privacy Policy</a>.
</p> </p>
</div>
<% if (typeof response !== "undefined" && typeof response.numFound !== "undefined" && typeof response.docs !== "undefined" && typeof highlighting !== "undefined") { %>
<div id="searchResults" class="mt-3 shadow-lg">
<!-- Search results will be dynamically populated here -->
<% if (response.numFound == 0) { %>
<p>No documents found matching the search query.</p>
<% } else { %>
<ul class="list-group shadow-lg">
<% response.docs.forEach(doc => { %>
<li class="list-group-item list-group-item-action flex-column align-items-start">
<h5><%= doc.title %></h5>
<% if (highlighting[doc.id] && highlighting[doc.id].text) { %>
<% highlighting[doc.id].text.forEach(snippet => { %>
<pre><%- h.stripWebVTT(snippet) %></pre>
<% }); %>
<% } else { %>
<!-- <p>No highlight available.</p> -->
<% } %>
<a href="<%= doc.url %>"><%= doc.url %></a>
</li>
<% }); %>
</ul>
<% } %>
</div>
</div> </div>
<% } %> <% if (typeof response !== "undefined" && typeof response.numFound !== "undefined" && typeof response.docs !== "undefined" && typeof highlighting !== "undefined") { %>
<!-- Pagination controls --> <div id="searchResults" class="mt-3 shadow-lg">
<% if (typeof totalPages !== "undefined" && totalPages) { %> <!-- Search results will be dynamically populated here -->
<nav aria-label="Search results pagination"> <% if (response.numFound == 0) { %>
<ul class="pagination justify-content-center mt-4"> <p>No documents found matching the search query.</p>
<% if (page > 1) { %> <% } else { %>
<li class="page-item"> <ul class="list-group shadow-lg">
<a class="page-link" href="/search?q=<%= query %>&page=<%= page - 1 %>&pageSize=<%= pageSize %>">Previous</a> <% response.docs.forEach(doc => { %>
</li> <li class="list-group-item list-group-item-action flex-column align-items-start">
<% } %> <h5><%= doc.title %></h5>
<% for (let i = Math.max(Math.min(page - 7, totalPages - 14), 1); i <= Math.min(totalPages, Math.max(page - 7, 1) + 14); i++) { %> <% if (highlighting[doc.id] && highlighting[doc.id].text) { %>
<li class="page-item <%= i == page ? 'active' : '' %>"> <% highlighting[doc.id].text.forEach(snippet => { %>
<a class="page-link" href="/search?q=<%= query %>&page=<%= i %>&pageSize=<%= pageSize %>"><%= i %></a> <pre><%- h.stripWebVTT(snippet) %></pre>
</li> <% }); %>
<% } %> <% } else { %>
<% if (page < totalPages) { %> <!-- <p>No highlight available.</p> -->
<li class="page-item"> <% } %>
<a class="page-link" href="/search?q=<%= query %>&page=<%= parseInt(page) + 1 %>&pageSize=<%= pageSize %>">Next</a> <a href="<%= doc.url %>"><%= doc.url %></a>
</li> </li>
<% } %> <% }); %>
</ul> </ul>
</nav> <% } %>
<p class="center">Page <%= page %> out of <%= totalPages %>. Displaying results <%= (page-1)*pageSize+1 %> through <%= Math.min(page*pageSize, totalResults) %> out of <%= totalResults %> total results.</p> </div>
<% } %> </div>
</main> <% } %>
<!-- Pagination controls -->
<%- include('./includes/bottom-navbar.ejs') %> <% if (typeof totalPages !== "undefined" && totalPages) { %>
<%- include('./includes/bottom-scripts.ejs') %> <nav aria-label="Search results pagination">
</body> <ul class="pagination justify-content-center mt-4">
</html> <% if (page > 1) { %>
<li class="page-item">
<a class="page-link" href="/search?q=<%= query %>&page=<%= page - 1 %>&pageSize=<%= pageSize %>">Previous</a>
</li>
<% } %>
<% for (let i = Math.max(Math.min(page - 7, totalPages - 14), 1); i <= Math.min(totalPages, Math.max(page - 7, 1) + 14); i++) { %>
<li class="page-item <%= i == page ? 'active' : '' %>">
<a class="page-link" href="/search?q=<%= query %>&page=<%= i %>&pageSize=<%= pageSize %>"><%= i %></a>
</li>
<% } %>
<% if (page < totalPages) { %>
<li class="page-item">
<a class="page-link" href="/search?q=<%= query %>&page=<%= parseInt(page) + 1 %>&pageSize=<%= pageSize %>">Next</a>
</li>
<% } %>
</ul>
</nav>
<p class="center">Page <%= page %> out of <%= totalPages %>. Displaying results <%= (page-1)*pageSize+1 %> through <%= Math.min(page*pageSize, totalResults) %> out of <%= totalResults %> total results.</p>
<% } %>
</main>
<%- include('./includes/bottom-navbar.ejs') %>
<%- include('./includes/bottom-scripts.ejs') %>
</body>
</html>

View File

@ -4,17 +4,13 @@
<title><%=h.getDirectoryTitle(directory)%></title> <title><%=h.getDirectoryTitle(directory)%></title>
<%- include('./includes/common-head.ejs') %> <%- include('./includes/common-head.ejs') %>
</head> </head>
<body onload="initPage()"> <body onload="initPage()">
<%- include('./includes/top-navbar.ejs') %> <%- include('./includes/top-navbar.ejs') %>
<%- include('./includes/no-trash-svg.ejs') %> <%- include('./includes/no-trash-svg.ejs') %>
<main class="container"> <main class="container">
<header> <header>
<%- include('./includes/breadcrumbs.ejs') %> <%- include('./includes/breadcrumbs.ejs') %>
</header> </header>
<% if (typeof videoURL !== 'undefined') {%> <% if (typeof videoURL !== 'undefined') {%>
<div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg"> <div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg">
<div class="col-lg-12 p-3 p-lg-5 pt-lg-3"> <div class="col-lg-12 p-3 p-lg-5 pt-lg-3">
@ -49,7 +45,6 @@
</div> </div>
</div> </div>
<% } %> <% } %>
<% if (typeof subtitleVTT !== 'undefined') {%> <% if (typeof subtitleVTT !== 'undefined') {%>
<div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg" style="max-height:65vh;overflow-y:scroll"> <div class="row p-4 pb-0 pe-lg-0 pt-lg-5 align-items-center rounded-3 border shadow-lg" style="max-height:65vh;overflow-y:scroll">
<div class="col-lg-12 p-3 p-lg-5 pt-lg-3"> <div class="col-lg-12 p-3 p-lg-5 pt-lg-3">
@ -65,9 +60,7 @@
</div> </div>
</div> </div>
<% } %> <% } %>
</main> </main>
<%- include('./includes/bottom-navbar.ejs') %> <%- include('./includes/bottom-navbar.ejs') %>
<%- include('./includes/bottom-scripts.ejs') %> <%- include('./includes/bottom-scripts.ejs') %>
</body> </body>