Use canonical paths for editor and fix route transitions
This commit is contained in:
+54
-22
@@ -24,21 +24,20 @@
|
|||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
<main class="transition-wrapper">
|
<main class="transition-wrapper">
|
||||||
<RouterView v-slot="{ Component }">
|
<Transition
|
||||||
<Transition
|
:name="routeTransitionName"
|
||||||
:name="routeTransitionName"
|
@after-enter="store.transitionDirection = 'none'"
|
||||||
@after-enter="store.transitionDirection = 'none'"
|
>
|
||||||
>
|
<div :key="routeViewKey" class="explorer-content">
|
||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="routeViewComponent"
|
||||||
:key="routeViewKey"
|
:key="routeViewKey"
|
||||||
class="explorer-content"
|
|
||||||
v-bind="routeViewProps"
|
v-bind="routeViewProps"
|
||||||
/>
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</Transition>
|
</div>
|
||||||
</RouterView>
|
</Transition>
|
||||||
</main>
|
</main>
|
||||||
<footer v-if="store.selected.size || store.uprogress.total || store.dprogress.total">
|
<footer v-if="store.selected.size || store.uprogress.total || store.dprogress.total">
|
||||||
<SelectionToolbar :path="path.pathList" />
|
<SelectionToolbar :path="path.pathList" />
|
||||||
@@ -50,16 +49,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type HeaderMain from '@/components/HeaderMain.vue'
|
import type HeaderMain from '@/components/HeaderMain.vue'
|
||||||
import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS'
|
import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS'
|
||||||
|
import { getDocuments } from '@/stores/documentStore'
|
||||||
import { useMainStore } from '@/stores/main'
|
import { useMainStore } from '@/stores/main'
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { RouterView } from 'vue-router'
|
|
||||||
|
|
||||||
import Router from '@/router/index'
|
import Router from '@/router/index'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import AboutModal from './components/AboutModal.vue'
|
import AboutModal from './components/AboutModal.vue'
|
||||||
import AccessDeniedModal from './components/AccessDeniedModal.vue'
|
import AccessDeniedModal from './components/AccessDeniedModal.vue'
|
||||||
|
import ExplorerView from './views/ExplorerView.vue'
|
||||||
import SelectionToolbar from './components/SelectionToolbar.vue'
|
import SelectionToolbar from './components/SelectionToolbar.vue'
|
||||||
|
import TextEditorView from './views/TextEditorView.vue'
|
||||||
import type SettingsModalVue from './components/SettingsModal.vue'
|
import type SettingsModalVue from './components/SettingsModal.vue'
|
||||||
import UserManagementModal from './components/UserManagementModal.vue'
|
import UserManagementModal from './components/UserManagementModal.vue'
|
||||||
import UserTokensModal from './components/UserTokensModal.vue'
|
import UserTokensModal from './components/UserTokensModal.vue'
|
||||||
@@ -67,6 +68,7 @@ import type { SortOrder } from './utils/docsort'
|
|||||||
|
|
||||||
interface Path {
|
interface Path {
|
||||||
path: string
|
path: string
|
||||||
|
canonicalPath: string
|
||||||
isEditorPath: boolean
|
isEditorPath: boolean
|
||||||
pathList: string[]
|
pathList: string[]
|
||||||
breadcrumbPathList: string[]
|
breadcrumbPathList: string[]
|
||||||
@@ -74,26 +76,42 @@ interface Path {
|
|||||||
query: string
|
query: string
|
||||||
}
|
}
|
||||||
const store = useMainStore()
|
const store = useMainStore()
|
||||||
|
|
||||||
|
const getDocByPath = (fullPath: string) =>
|
||||||
|
getDocuments().find(doc => (doc.loc ? `${doc.loc}/${doc.name}` : doc.name) === fullPath)
|
||||||
|
|
||||||
const path: ComputedRef<Path> = computed(() => {
|
const path: ComputedRef<Path> = computed(() => {
|
||||||
const p = decodeURIComponent(Router.currentRoute.value.path).split('//')
|
const p = decodeURIComponent(Router.currentRoute.value.path).split('//')
|
||||||
const routePathList = (p[0] ?? '').split('/').filter(value => value !== '')
|
const rawPath = p[0] ?? ''
|
||||||
|
const routePathList = rawPath.split('/').filter(value => value !== '')
|
||||||
const query = p.slice(1).join('//')
|
const query = p.slice(1).join('//')
|
||||||
const isEditorPath = routePathList[0] === 'edit'
|
const fullPath = routePathList.join('/')
|
||||||
const pathList = isEditorPath ? routePathList.slice(1, -1) : routePathList
|
// Access docVersion to make route mode reactive to tree updates
|
||||||
const breadcrumbPathList = isEditorPath
|
void store.docVersion
|
||||||
? routePathList.slice(1)
|
const doc = fullPath ? getDocByPath(fullPath) : null
|
||||||
: routePathList
|
const isEditorPath = !!(doc && !doc.dir && doc.text)
|
||||||
|
const canonicalBase = !fullPath
|
||||||
|
? '/'
|
||||||
|
: doc?.dir
|
||||||
|
? `/${fullPath}/`
|
||||||
|
: `/${fullPath}`
|
||||||
|
const canonicalPath = query
|
||||||
|
? rawPath // keep search URL shape untouched
|
||||||
|
: canonicalBase
|
||||||
|
const pathList = isEditorPath ? routePathList.slice(0, -1) : routePathList
|
||||||
|
const breadcrumbPathList = routePathList
|
||||||
const breadcrumbLinks = isEditorPath
|
const breadcrumbLinks = isEditorPath
|
||||||
? [
|
? [
|
||||||
'/',
|
'/',
|
||||||
...routePathList
|
...routePathList
|
||||||
.slice(1, -1)
|
.slice(0, -1)
|
||||||
.map((_, index) => `/${routePathList.slice(1, index + 2).join('/')}/`),
|
.map((_, index) => `/${routePathList.slice(0, index + 1).join('/')}/`),
|
||||||
`/${routePathList.join('/')}`
|
`/${fullPath}`
|
||||||
]
|
]
|
||||||
: undefined
|
: undefined
|
||||||
return {
|
return {
|
||||||
path: p[0] ?? '',
|
path: rawPath,
|
||||||
|
canonicalPath,
|
||||||
isEditorPath,
|
isEditorPath,
|
||||||
pathList,
|
pathList,
|
||||||
breadcrumbPathList,
|
breadcrumbPathList,
|
||||||
@@ -106,15 +124,29 @@ const routeTransitionName = computed(() => {
|
|||||||
if (store.transitionDirection === 'backward') return 'slide-backward'
|
if (store.transitionDirection === 'backward') return 'slide-backward'
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
const routeViewComponent = computed(() =>
|
||||||
|
path.value.isEditorPath ? TextEditorView : ExplorerView
|
||||||
|
)
|
||||||
const routeViewKey = computed(() => {
|
const routeViewKey = computed(() => {
|
||||||
const route = Router.currentRoute.value
|
return path.value.isEditorPath
|
||||||
return route.name === 'editor' ? route.path : String(route.name ?? route.path)
|
? `editor:${path.value.path}`
|
||||||
|
: 'explorer'
|
||||||
})
|
})
|
||||||
const routeViewProps = computed(() =>
|
const routeViewProps = computed(() =>
|
||||||
path.value.isEditorPath
|
path.value.isEditorPath
|
||||||
? {}
|
? {}
|
||||||
: { path: path.value.pathList, query: path.value.query }
|
: { path: path.value.pathList, query: path.value.query }
|
||||||
)
|
)
|
||||||
|
watch(
|
||||||
|
() => path.value.canonicalPath,
|
||||||
|
canonical => {
|
||||||
|
const current = decodeURIComponent(Router.currentRoute.value.path)
|
||||||
|
if (canonical && current !== canonical) {
|
||||||
|
Router.replace(canonical.replaceAll('?', '%3F').replaceAll('#', '%23'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
watch(
|
watch(
|
||||||
() => path.value.path,
|
() => path.value.path,
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ onUnmounted(() => {
|
|||||||
clearInterval(modifiedTimer)
|
clearInterval(modifiedTimer)
|
||||||
})
|
})
|
||||||
const editRoute = (path: string) =>
|
const editRoute = (path: string) =>
|
||||||
'/edit/' +
|
'/' +
|
||||||
path
|
path
|
||||||
.split('/')
|
.split('/')
|
||||||
.map(part => encodeURIComponent(part))
|
.map(part => encodeURIComponent(part))
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ onUnmounted(() => {
|
|||||||
// Re-seed aspect ratios whenever docs update (e.g., ar patch from server)
|
// Re-seed aspect ratios whenever docs update (e.g., ar patch from server)
|
||||||
watch(() => props.documents, seedFromDocs)
|
watch(() => props.documents, seedFromDocs)
|
||||||
const editRoute = (path: string) =>
|
const editRoute = (path: string) =>
|
||||||
'/edit/' +
|
'/' +
|
||||||
path
|
path
|
||||||
.split('/')
|
.split('/')
|
||||||
.map(part => encodeURIComponent(part))
|
.map(part => encodeURIComponent(part))
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export class Doc {
|
|||||||
get editurl(): string {
|
get editurl(): string {
|
||||||
if (!this.text) return ''
|
if (!this.text) return ''
|
||||||
const p = this.loc ? `${this.loc}/${this.name}` : this.name
|
const p = this.loc ? `${this.loc}/${this.name}` : this.name
|
||||||
return '/#/edit/' + p.replaceAll('?', '%3F').replaceAll('#', '%23')
|
return '/#/' + p.replaceAll('?', '%3F').replaceAll('#', '%23')
|
||||||
}
|
}
|
||||||
get complete(): boolean {
|
get complete(): boolean {
|
||||||
return !this.ghost && (this.dir || this.size <= this.allocated)
|
return !this.ghost && (this.dir || this.size <= this.allocated)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useMainStore } from '@/stores/main'
|
import { useMainStore } from '@/stores/main'
|
||||||
import ExplorerView from '@/views/ExplorerView.vue'
|
import ExplorerView from '@/views/ExplorerView.vue'
|
||||||
import TextEditorView from '@/views/TextEditorView.vue'
|
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
|
||||||
function getPathDepth(path: string): number {
|
function getPathDepth(path: string): number {
|
||||||
@@ -11,11 +10,6 @@ function getPathDepth(path: string): number {
|
|||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
|
||||||
path: '/edit/:pathMatch(.*)*',
|
|
||||||
name: 'editor',
|
|
||||||
component: TextEditorView
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'explorer',
|
name: 'explorer',
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ const store = useMainStore()
|
|||||||
|
|
||||||
const MAX_SIZE = 1024 * 1024 // 1 MiB
|
const MAX_SIZE = 1024 * 1024 // 1 MiB
|
||||||
|
|
||||||
const filePath = computed(() => decodeURIComponent(route.path.slice(6))) // strip /edit/
|
const filePath = computed(() => {
|
||||||
|
const raw = decodeURIComponent(route.path).split('//')[0] ?? ''
|
||||||
|
return raw.replace(/^\//, '').replace(/\/$/, '')
|
||||||
|
})
|
||||||
const filename = computed(() => filePath.value.split('/').pop() || '')
|
const filename = computed(() => filePath.value.split('/').pop() || '')
|
||||||
|
|
||||||
const filesUrl = computed(() => {
|
const filesUrl = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user