diff --git a/navigator/src/main.js b/navigator/src/main.js index c6b340b..3c30eb3 100644 --- a/navigator/src/main.js +++ b/navigator/src/main.js @@ -36,17 +36,15 @@ const errorHandler = (error, title = "System Error") => { let lastValidRoutePath = null; router.beforeEach(async (to, from) => { - if (to.name === 'root') - return localStorage.getItem(lastPathStorageKey) ?? '/browse/'; if (to.fullPath === lastValidRoutePath) { return true; // ignore from updating window.location.hash } - const host = to.params.host.replace(/^\/|:$/g, '') || undefined; + const host = to.params.host || undefined; if (to.name === 'browse') { try { let realPath = (await useSpawn(['realpath', '--canonicalize-existing', to.params.path], { superuser: 'try', host }).promise()).stdout.trim(); if (to.params.path !== realPath) - return `/browse${to.params.host}${realPath}`; + return `/browse/${to.params.host}${realPath}`; try { await useSpawn(['test', '-r', to.params.path, '-a', '-x', to.params.path], { superuser: 'try', host }).promise(); } catch { @@ -58,10 +56,10 @@ router.beforeEach(async (to, from) => { errorHandler(errorStringHTML(error), "Failed to open path"); return false; } + localStorage.setItem(lastPathStorageKey, to.fullPath); } lastValidRoutePath = to.fullPath; // protect double-update from next line window.location.hash = '#' + to.fullPath; // needed to update URL in address bar - localStorage.setItem(lastPathStorageKey, to.fullPath); return true; }) diff --git a/navigator/src/router/index.js b/navigator/src/router/index.js index 988363d..b8c21e3 100644 --- a/navigator/src/router/index.js +++ b/navigator/src/router/index.js @@ -1,17 +1,23 @@ import { createRouter, createWebHashHistory } from 'vue-router'; +import { lastPathStorageKey } from '../keys'; const routes = [ { - path: '/browse:host(/[^/:]+:?[0-9]*:)?:path(/.*)', + path: '/browse', + redirect: () => `/browse/${cockpit.transport.host ?? 'localhost'}/`, + }, + { + path: '/browse/:host([^/]+)', + redirect: route => `${route.fullPath}/`, + }, + { + path: '/browse/:host([^/]+):path(/.*)', + strict: true, name: 'browse', component: () => import('../views/Browser.vue'), }, { - path: '/browse:host(/[^/:]+:?[0-9]*:)?', - redirect: route => `${route.fullPath}/` - }, - { - path: '/edit:host(/[^/:]+:?[0-9]*:)?:path(/.+)', + path: '/edit/:host:path(/.+)', name: 'edit', component: () => import('../views/Editor.vue'), }, @@ -29,6 +35,7 @@ const routes = [ { path: '/', name: 'root', + redirect: () => localStorage.getItem(lastPathStorageKey) ?? `/browse/${cockpit.transport.host ?? 'localhost'}/`, } ]; diff --git a/navigator/src/views/Browser.vue b/navigator/src/views/Browser.vue index ee4f87e..4aa7174 100644 --- a/navigator/src/views/Browser.vue +++ b/navigator/src/views/Browser.vue @@ -84,6 +84,14 @@ import PathBreadCrumbs from '../components/PathBreadCrumbs.vue'; import { notificationsInjectionKey, pathHistoryInjectionKey, lastPathStorageKey, settingsInjectionKey } from '../keys'; import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, RefreshIcon, ChevronDownIcon, SearchIcon } from '@heroicons/vue/solid'; +const encodePartial = (string) => + encodeURIComponent(string) + .replace(/%40/g, '@') + .replace(/%3D/g, '=') + .replace(/%2B/g, '+') + .replace(/%2F/g, '/') + .replace(/%20/g, ' '); + export default { setup() { const settings = inject(settingsInjectionKey); @@ -124,9 +132,9 @@ export default { }); const cd = ({ path, host }) => { - const newHost = host ?? (route.params.host ? pathHistory.current().host : ""); - const newPath = path ?? (pathHistory.current().path); - router.push(`/browse${newHost ? `/${newHost}:` : ''}${newPath}`); + const newHost = host ?? (pathHistory.current().host); + const newPath = encodePartial(path ?? (pathHistory.current().path)); + router.push(`/browse/${newHost}${newPath}`); }; const back = () => { @@ -144,7 +152,7 @@ export default { } const openEditor = (path) => { - router.push(`/edit${route.params.host ? `/${pathHistory.current().host}:` : ''}${path}`); + router.push(`/edit/${pathHistory.current().host}${encodePartial(path)}`); } const getSelected = () => directoryViewRef.value?.getSelected?.() ?? []; @@ -159,12 +167,13 @@ export default { ); }, { immediate: true }); - watch([() => route.params.path, () => route.params.host], async () => { + watch(route, async () => { if (route.name !== 'browse') return; - const host = route.params.host?.replace(/^\/|:$/g, '') || cockpit.transport.host; - if (pathHistory.current()?.path !== route.params.path || pathHistory.current()?.host !== host) { - pathHistory.push({ path: route.params.path, host }); // updates actual view + const host = route.params.host; + const path = decodeURIComponent(route.params.path); + if (pathHistory.current()?.path !== path || pathHistory.current()?.host !== host) { + pathHistory.push({ path, host }); // updates actual view } }, { immediate: true });