From b1d715f57e4493ad15f1f6fe7ac49d26511da557 Mon Sep 17 00:00:00 2001 From: Jannis Baum Date: Mon, 22 Jul 2024 17:54:11 +0200 Subject: [PATCH] feat(#103): open at scroll on same tab --- package.json | 1 + src/app.ts | 44 ++---------------------- src/cli.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++ src/routes/viewer.ts | 2 +- viv | 4 +-- yarn.lock | 47 ++++++++++++++++++++++++- 6 files changed, 134 insertions(+), 46 deletions(-) create mode 100644 src/cli.ts diff --git a/package.json b/package.json index cc233cd6..f2f499c4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@viz-js/viz": "^3.7.0", "ansi_up": "^6.0.2", + "axios": "^1.7.2", "express": "^4.19.2", "glob": "10.4.5", "highlight.js": "^11.10.0", diff --git a/src/app.ts b/src/app.ts index d5db496d..e7ee5d55 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,16 +1,14 @@ import { createServer, get } from 'http'; -import { resolve as presolve } from 'path'; import express from 'express'; -import open from 'open'; import config from './parser/config.js'; 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, preferredPath, urlToPath } from './utils/path.js'; -import { existsSync } from 'fs'; +import { urlToPath } from './utils/path.js'; +import { address, handleArgs } from './cli.js'; const app = express(); app.use(express.json()); @@ -39,44 +37,6 @@ export const { clientsAt, messageClients } = setupSockets( }, ); -const address = `http://localhost:${config.port}`; -const handleArgs = async () => { - try { - const args = process.argv.slice(2); - const options = args.filter((arg) => arg.startsWith('-')); - for (const option of options) { - switch (option) { - case '-v': - case '--version': - console.log(`vivify-server ${process.env.VERSION ?? 'dev'}`); - break; - default: - console.log(`unknown option "${option}"`); - } - } - - const paths = args.filter((arg) => !arg.startsWith('-')); - await Promise.all( - paths.map(async (path) => { - if (!existsSync(path)) { - console.log(`File not found: ${path}`); - return; - } - const target = preferredPath(presolve(path)); - const url = `${address}${pathToURL(target)}`; - await open(url); - }), - ); - } finally { - if (process.env['NODE_ENV'] !== 'development') { - // - viv executable waits for this string and then stops printing - // vivify-server's output and terminates - // - the string itself is not shown to the user - console.log('STARTUP COMPLETE'); - } - } -}; - get(`${address}/health`, async () => { // server is already running await handleArgs(); diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 00000000..3bb9b5bc --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,82 @@ +import { existsSync } from 'fs'; +import { resolve as presolve } from 'path'; +import config from './parser/config.js'; +import open from 'open'; +import { pathToURL, preferredPath } from './utils/path.js'; +import axios from 'axios'; + +export const address = `http://localhost:${config.port}`; + +const openTarget = async (path: string, scroll: string | undefined) => { + // scroll position is zero-char seperated because that + // character is not allowed in file paths + if (!existsSync(path)) { + console.error(`File not found: ${path}`); + return; + } + + const resolvedPath = presolve(path); + const absoluteURL = `${address}${pathToURL(resolvedPath)}`; + const preferredURL = `${address}${pathToURL(preferredPath(resolvedPath))}`; + // if scroll position is provided + if (scroll !== undefined) { + // we send scroll request to clients + const { + data: { clients }, + } = await axios.post<{ clients: number }>(absoluteURL, { + cursor: scroll, + }); + // if there were clients, we can just open the plain + // URL/existing tab because it will have scrolled + if (!clients) { + // if not we open a new tab at the scroll position + await open(preferredURL + `?cursor=${scroll}`); + return; + } + } + await open(preferredURL, { newInstance: false }); +}; + +export const handleArgs = async () => { + try { + const args = process.argv.slice(2); + const parsed: { target?: string; scroll?: string } = {}; + const setArg = (arg: keyof typeof parsed, value: string) => { + if (parsed[arg]) { + console.error(`Duplicate argument for "${arg}", skipping`); + } else { + parsed[arg] = value; + } + }; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (!arg.startsWith('-')) { + setArg('target', arg); + continue; + } + switch (arg) { + case '-v': + case '--version': + console.log(`vivify-server ${process.env.VERSION ?? 'dev'}`); + break; + case '-s': + case '--scroll': + setArg('scroll', args[++i]); + break; + default: + console.log(`Unknown option "${arg}"`); + } + } + if (parsed.target) { + await openTarget(parsed.target, parsed.scroll); + } + } finally { + if (process.env['NODE_ENV'] !== 'development') { + // - viv executable waits for this string and then stops printing + // vivify-server's output and terminates + // - the string itself is not shown to the user + console.log('STARTUP COMPLETE'); + } + } +}; diff --git a/src/routes/viewer.ts b/src/routes/viewer.ts index 88006f7f..4fcfcace 100644 --- a/src/routes/viewer.ts +++ b/src/routes/viewer.ts @@ -26,7 +26,7 @@ const pageTitle = (path: string) => { if (config.preferHomeTilde) { router.use((req, res, next) => { if (req.method === 'GET' && req.path.startsWith(homedir())) { - res.redirect(req.baseUrl + req.path.replace(homedir(), '/~')); + res.redirect(req.originalUrl.replace(homedir(), '/~')); } else { next(); } diff --git a/viv b/viv index 30f6514c..683f9ba3 100755 --- a/viv +++ b/viv @@ -1,8 +1,8 @@ #!/bin/sh print_usage() { - echo "viv [--help] files/directories" - echo "View files/directories in browser and lazily start vivify-server" + echo "viv [--help] file/directory" + echo "View file/directory in browser and lazily start vivify-server" } if [ "$#" -lt 1 -o "$1" = "-h" -o "$1" = "--help" ]; then diff --git a/yarn.lock b/yarn.lock index 0a7c953a..ba6a3065 100644 --- a/yarn.lock +++ b/yarn.lock @@ -700,6 +700,20 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -856,6 +870,13 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" @@ -961,6 +982,11 @@ define-lazy-prop@^3.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -1331,6 +1357,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + foreground-child@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" @@ -1339,6 +1370,15 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1855,7 +1895,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2120,6 +2160,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"