From 78e3554a48b5937c4d235b6a96767b488b3eba30 Mon Sep 17 00:00:00 2001 From: Jannis Baum Date: Sat, 13 Jul 2024 06:07:48 +0200 Subject: [PATCH 01/12] feat(#35): config to prefer home tilde --- README.md | 3 +++ src/app.ts | 6 +++--- src/parser/config.ts | 2 ++ src/routes/viewer.ts | 17 ++++++++++++++--- src/utils/path.ts | 18 ++++++++++++------ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 404dbef6..9449e866 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ following optional keys: files will be displayed as monospaced text with code highlighting if available. Default Markdown extensions are `['markdown', 'md', 'mdown', 'mdwn', 'mkd', 'mkdn']` +- **`"preferHomeTilde"`**\ + Prefer using `~` as a placeholder for your home directory in URLs as well as + the `compoments` for `"pageTitle"` (default is `true`) ## Installation diff --git a/src/app.ts b/src/app.ts index c52d927f..8424e96e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,7 +9,7 @@ import { router as healthRouter } from './routes/health.js'; import { router as staticRouter } from './routes/static.js'; import { router as viewerRouter } from './routes/viewer.js'; import { setupSockets } from './sockets.js'; -import { pathToURL, urlToPath } from './utils/path.js'; +import { pathToURL, preferredPath, urlToPath } from './utils/path.js'; import { existsSync } from 'fs'; const app = express(); @@ -48,8 +48,8 @@ const openArgs = async () => { console.log(`File not found: ${path}`); return; } - const absolute = presolve(path); - const url = `${address}${pathToURL(absolute)}`; + const target = preferredPath(presolve(path)); + const url = `${address}${pathToURL(target)}`; await open(url); }), ); diff --git a/src/parser/config.ts b/src/parser/config.ts index 6163fe78..60aad88d 100644 --- a/src/parser/config.ts +++ b/src/parser/config.ts @@ -10,12 +10,14 @@ type Config = { pageTitle?: string; mdExtensions: string[]; timeout: number; + preferHomeTilde: boolean; }; const defaultConfig: Config = { port: 31622, mdExtensions: ['markdown', 'md', 'mdown', 'mdwn', 'mkd', 'mkdn'], timeout: 10000, + preferHomeTilde: true, }; const envConfigs: [string, keyof Config][] = [ diff --git a/src/routes/viewer.ts b/src/routes/viewer.ts index 84f39b12..d7cd6182 100644 --- a/src/routes/viewer.ts +++ b/src/routes/viewer.ts @@ -1,11 +1,12 @@ import { lstatSync, readFileSync } from 'fs'; import { dirname as pdirname, join as pjoin } from 'path'; +import { homedir } from 'os'; import { Request, Response, Router } from 'express'; import { messageClientsAt } from '../app.js'; import config from '../parser/config.js'; -import { pathToURL, pcomponents, pmime } from '../utils/path.js'; +import { absPath, pathToURL, pcomponents, pmime, preferredPath } from '../utils/path.js'; import { renderDirectory, renderTextFile } from '../parser/parser.js'; export const router = Router(); @@ -13,7 +14,7 @@ export const router = Router(); const liveContent = new Map(); const pageTitle = (path: string) => { - const comps = pcomponents(path); + const comps = pcomponents(preferredPath(path)); if (config.pageTitle) { return eval(` const components = ${JSON.stringify(comps)}; @@ -22,6 +23,16 @@ const pageTitle = (path: string) => { } else return pjoin(...comps.slice(-2)); }; +if (config.preferHomeTilde) { + router.use((req, res, next) => { + if (req.method === 'GET' && req.path.startsWith(homedir())) { + res.redirect(req.path.replace(homedir(), '/~')); + } else { + next(); + } + }); +} + router.get(/.*/, async (req: Request, res: Response) => { const path = res.locals.filepath; @@ -74,7 +85,7 @@ router.get(/.*/, async (req: Request, res: Response) => { diff --git a/src/utils/path.ts b/src/utils/path.ts index 27cecb0b..38cd7e24 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,6 +1,7 @@ import { execSync } from 'child_process'; import { homedir } from 'os'; import { basename as pbasename, dirname as pdirname, parse as pparse } from 'path'; +import config from '../parser/config.js'; export const pmime = (path: string) => execSync(`file --mime-type -b '${path}'`).toString().trim(); @@ -9,7 +10,7 @@ export const pcomponents = (path: string) => { const components = new Array(); // directory let dir = parsed.dir; - while (dir !== '/' && dir !== '') { + while (dir !== '/' && dir !== '.') { components.unshift(pbasename(dir)); dir = pdirname(dir); } @@ -20,12 +21,17 @@ export const pcomponents = (path: string) => { return components; }; +export const absPath = (path: string) => path.replace(/^\/~/, homedir()).replace(/\/+$/, ''); + export const urlToPath = (url: string) => { - const path = decodeURIComponent(url.replace(/^\/(viewer|health)/, '')) - .replace(/^\/~/, homedir()) - .replace(/\/+$/, ''); + const path = absPath(decodeURIComponent(url.replace(/^\/(viewer|health)/, ''))); return path === '' ? '/' : path; }; -export const pathToURL = (path: string, route: string = 'viewer') => - `/${route}${encodeURIComponent(path).replaceAll('%2F', '/')}`; +export const pathToURL = (path: string, route: string = 'viewer') => { + const withoutPrefix = path.startsWith('/') ? path.slice(1) : path; + return `/${route}/${encodeURIComponent(withoutPrefix).replaceAll('%2F', '/')}`; +}; + +export const preferredPath = (path: string): string => + config.preferHomeTilde && path.startsWith(homedir()) ? path.replace(homedir(), '~') : path; From e2b5fd1a4cf0a7d36d6aac5a882f6ca274d02e8c Mon Sep 17 00:00:00 2001 From: Jannis Baum Date: Tue, 16 Jul 2024 09:10:10 +0200 Subject: [PATCH 02/12] feat(#35): multiple style sheets --- src/parser/config.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/parser/config.ts b/src/parser/config.ts index 60aad88d..789c7ff8 100644 --- a/src/parser/config.ts +++ b/src/parser/config.ts @@ -30,6 +30,20 @@ const configPaths = [ path.join(homedir(), '.vivify.json'), ]; +// read contents of file at paths or files at paths +const getFileContents = (paths: string[] | string | undefined): string => { + if (paths === undefined) return ''; + const getFileContent = (p: string): string => { + const resolved = p[0] === '~' ? path.join(homedir(), p.slice(1)) : p; + return fs.existsSync(resolved) ? fs.readFileSync(resolved, 'utf8') : ''; + }; + + if (Array.isArray(paths)) { + return paths.map(getFileContent).join('\n'); + } + return getFileContent(paths); +}; + const getConfig = (): Config => { let config = undefined; // greedily get config @@ -44,11 +58,8 @@ const getConfig = (): Config => { if (config === undefined) return defaultConfig; // get styles - if (config.styles && config.styles.length > 0) { - const stylePath = - config.styles[0] === '~' ? path.join(homedir(), config.styles.slice(1)) : config.styles; - config.styles = fs.existsSync(stylePath) ? fs.readFileSync(stylePath, 'utf8') : ''; - } + config.styles = getFileContents(config.styles); + // fill missing values from default config for (const [key, value] of Object.entries(defaultConfig)) { if (config[key] === undefined) config[key] = value; From eddddac9981e72ce1ee64711b25f608d184b4c5a Mon Sep 17 00:00:00 2001 From: Jannis Baum Date: Tue, 16 Jul 2024 09:17:13 +0200 Subject: [PATCH 03/12] feat(#35): custom js --- src/parser/config.ts | 4 +++- src/routes/viewer.ts | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/parser/config.ts b/src/parser/config.ts index 789c7ff8..a48ed8ca 100644 --- a/src/parser/config.ts +++ b/src/parser/config.ts @@ -4,6 +4,7 @@ import path from 'path'; type Config = { styles?: string; + scripts?: string; port: number; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ katexOptions?: any; @@ -57,8 +58,9 @@ const getConfig = (): Config => { if (config === undefined) return defaultConfig; - // get styles + // get styles & scripts config.styles = getFileContents(config.styles); + config.scripts = getFileContents(config.scripts); // fill missing values from default config for (const [key, value] of Object.entries(defaultConfig)) { diff --git a/src/routes/viewer.ts b/src/routes/viewer.ts index d7cd6182..b94cb9b4 100644 --- a/src/routes/viewer.ts +++ b/src/routes/viewer.ts @@ -74,15 +74,14 @@ router.get(/.*/, async (req: Request, res: Response) => { - + ${config.styles ? `` : ''}
${body}
+ ${config.scripts ? `` : ''} ` : ''} + ${config.scripts ? `` : ''} `); From ee05606497044b7ea8f58a8eb31476d54d0532e2 Mon Sep 17 00:00:00 2001 From: Jannis Baum Date: Fri, 19 Jul 2024 09:58:35 +0200 Subject: [PATCH 12/12] docs(#35): fixes from review --- docs/customization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/customization.md b/docs/customization.md index c3d286c7..629f8750 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -41,10 +41,10 @@ following optional keys: - **`"pageTitle"`**\ JavaScript code that will be evaluated to determine the viewer's page title. Here, the variable `components` is set to a string array of path components - for the current file, e.g. `['/', 'Users', 'you', 'file.txt']`. If this + for the current file, e.g. `['~', 'some', 'path', 'file.txt']`. If this evaluation fails, the title will be *custom title error* and you will see the error message on the page. The default title are the last two components - joined with the path separator, e.g. `you/file.txt` + joined with the path separator, e.g. `path/file.txt` - **`"mdExtensions"`**\ An array of file extensions that Vivify will render as Markdown. All other files (except for Jupyter Notebooks) will be displayed as monospaced text with @@ -52,4 +52,4 @@ following optional keys: `['markdown', 'md', 'mdown', 'mdwn', 'mkd', 'mkdn']` - **`"preferHomeTilde"`**\ Prefer using `~` as a placeholder for your home directory in URLs as well as - the `compoments` for `"pageTitle"` (default is `true`) + the `components` for `"pageTitle"` (default is `true`)