Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion apps/files/src/components/FilesNavigationListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ import type { IView } from '@nextcloud/files'

import { getCanonicalLocale, getLanguage } from '@nextcloud/l10n'
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router/composables'
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import { useVisibleViews } from '../composables/useViews.ts'
import { folderTreeId } from '../services/FolderTree.ts'
import { useViewConfigStore } from '../store/viewConfig.ts'

/**
* Views whose main file list is driven by `dir`. The route often carries a `fileid` for the
* selected row; many actions switch `view` between `folders`, `files`, and `personal` while
* keeping the same directory — the folder tree should stay highlighted based on `dir` only.
*/
const PATH_BASED_LISTING_VIEW_IDS = new Set<string>([folderTreeId, 'files', 'personal'])

const props = withDefaults(defineProps<{
view: IView
level?: number
Expand All @@ -24,9 +32,22 @@ const props = withDefaults(defineProps<{
const maxLevel = 6 // Limit nesting to not exceed max call stack size
const viewConfigStore = useViewConfigStore()
const viewConfig = computed(() => viewConfigStore.viewConfigs[props.view.id])
const route = useRoute()
const isExpanded = computed(() => viewConfig.value
? (viewConfig.value.expanded === true)
: (props.view.expanded === true))
const isFolderTreeNode = computed(() => props.view.id.startsWith(`${folderTreeId}::`))
const isDirectoryActive = computed(() => {
if (!isFolderTreeNode.value) {
return false
}

const currentView = String(route.params?.view ?? '')
const currentDir = normalizeDir(route.query?.dir)
const viewDir = normalizeDir(props.view.params?.dir)

return PATH_BASED_LISTING_VIEW_IDS.has(currentView) && currentDir === viewDir
})
Comment on lines +39 to +50
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't really say I'm happy to see custom view code into a global component, but I don't really see a better way, that's correct yeah 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@susnux , any opinions ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a router issue / design problem.
Because the URL is bound to active file not active folder (/files/ACTIVEFILE?dir=ACTIVEFOLDER vs /files/ACTIVEFOLDER?file=ACTIVEFILE)

So this happens because we se the to of the view to /files/FOLDERID right?
Does it work if we instead just use "additional params" and use the dir param?

Copy link
Copy Markdown
Contributor Author

@mykh-hailo mykh-hailo Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@susnux I update some codes to make tree view selection works properly as expected.
For a technical overview, I have the FolderNavigationListItem to be active according to dir parameter of the route.
I'd appreciate it if you review and share any opinion on it.


const views = useVisibleViews()
const childViews = computed(() => {
Expand Down Expand Up @@ -66,6 +87,16 @@ const hasChildViews = computed(() => childViews.value.length > 0)
const navigationRoute = computed(() => {
if (props.view.params) {
const { dir } = props.view.params
// Folder tree: navigate by `dir` only (no `fileid` in the path). Matches how the list
// is located (`?dir=…`) even when a file row is selected (`/:fileid`).
if (isFolderTreeNode.value) {
const dirParam = typeof dir === 'string' ? dir : '/'
return {
name: 'filelist',
params: { view: folderTreeId },
query: { dir: dirParam },
}
}
return { name: 'filelist', params: { ...props.view.params }, query: { dir } }
}
return { name: 'filelist', params: { view: props.view.id } }
Expand Down Expand Up @@ -121,6 +152,18 @@ async function loadChildViews() {
}
}
}

/**
* Normalize directory paths for route comparisons.
*
* @param dir - the route directory to normalize
*/
function normalizeDir(dir: unknown): string {
if (typeof dir !== 'string' || dir.length === 0) {
return '/'
}
return dir.replace(/^(.+)\/$/, '$1')
}
</script>

<script lang="ts">
Expand All @@ -141,7 +184,8 @@ export default {
allow-collapse
:loading="isLoading"
:data-cy-files-navigation-item="view.id"
:exact="hasChildViews /* eslint-disable-line @nextcloud/vue/no-deprecated-props */"
:exact="isFolderTreeNode || hasChildViews /* eslint-disable-line @nextcloud/vue/no-deprecated-props */"
:active="isDirectoryActive"
:name="view.name"
:open="isExpanded"
:pinned="view.sticky"
Expand Down