mirror of
https://github.com/nextcloud/server.git
synced 2026-06-07 15:53:04 -04:00
fix(files): Do not show files from hidden folders in "Recent"-view if hidden files are disabled by user
Needed to adjust the store creation to be able to inject pinia before the vue app is initialized. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
6818790fd3
commit
2cf4f08353
8 changed files with 169 additions and 13 deletions
|
|
@ -1,8 +1,9 @@
|
||||||
import Vue from 'vue'
|
import { PiniaVuePlugin } from 'pinia'
|
||||||
import { createPinia, PiniaVuePlugin } from 'pinia'
|
|
||||||
import { getNavigation } from '@nextcloud/files'
|
import { getNavigation } from '@nextcloud/files'
|
||||||
import { getRequestToken } from '@nextcloud/auth'
|
import { getRequestToken } from '@nextcloud/auth'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
import { pinia } from './store/index.ts'
|
||||||
import router from './router/router'
|
import router from './router/router'
|
||||||
import RouterService from './services/RouterService'
|
import RouterService from './services/RouterService'
|
||||||
import SettingsModel from './models/Setting.js'
|
import SettingsModel from './models/Setting.js'
|
||||||
|
|
@ -30,7 +31,6 @@ Object.assign(window.OCP.Files, { Router })
|
||||||
|
|
||||||
// Init Pinia store
|
// Init Pinia store
|
||||||
Vue.use(PiniaVuePlugin)
|
Vue.use(PiniaVuePlugin)
|
||||||
const pinia = createPinia()
|
|
||||||
|
|
||||||
// Init Navigation Service
|
// Init Navigation Service
|
||||||
// This only works with Vue 2 - with Vue 3 this will not modify the source but return just a oberserver
|
// This only works with Vue 2 - with Vue 3 this will not modify the source but return just a oberserver
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,37 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import type { ContentsWithRoot } from '@nextcloud/files'
|
import type { ContentsWithRoot, Node } from '@nextcloud/files'
|
||||||
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
||||||
|
|
||||||
import { getCurrentUser } from '@nextcloud/auth'
|
import { getCurrentUser } from '@nextcloud/auth'
|
||||||
import { Folder, Permission, davGetRecentSearch, davGetClient, davResultToNode, davRootPath, davRemoteURL } from '@nextcloud/files'
|
import { Folder, Permission, davGetRecentSearch, davGetClient, davResultToNode, davRootPath, davRemoteURL } from '@nextcloud/files'
|
||||||
|
import { useUserConfigStore } from '../store/userconfig.ts'
|
||||||
|
import { pinia } from '../store/index.ts'
|
||||||
|
|
||||||
const client = davGetClient()
|
const client = davGetClient()
|
||||||
|
|
||||||
const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 14))
|
const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 14))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get recently changed nodes
|
||||||
|
*
|
||||||
|
* This takes the users preference about hidden files into account.
|
||||||
|
* If hidden files are not shown, then also recently changed files *in* hidden directories are filtered.
|
||||||
|
*
|
||||||
|
* @param path Path to search for recent changes
|
||||||
|
*/
|
||||||
export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
|
export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
|
||||||
|
const store = useUserConfigStore(pinia)
|
||||||
|
/**
|
||||||
|
* Filter function that returns only the visible nodes - or hidden if explicitly configured
|
||||||
|
* @param node The node to check
|
||||||
|
*/
|
||||||
|
const filterHidden = (node: Node) =>
|
||||||
|
path !== '/' // We need to hide files from hidden directories in the root if not configured to show
|
||||||
|
|| store.userConfig.show_hidden // If configured to show hidden files we can early return
|
||||||
|
|| !node.dirname.split('/').some((dir) => dir.startsWith('.')) // otherwise only include the file if non of the parent directories is hidden
|
||||||
|
|
||||||
const contentsResponse = await client.getDirectoryContents(path, {
|
const contentsResponse = await client.getDirectoryContents(path, {
|
||||||
details: true,
|
details: true,
|
||||||
data: davGetRecentSearch(lastTwoWeeksTimestamp),
|
data: davGetRecentSearch(lastTwoWeeksTimestamp),
|
||||||
|
|
@ -53,6 +73,6 @@ export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
|
||||||
owner: getCurrentUser()?.uid || null,
|
owner: getCurrentUser()?.uid || null,
|
||||||
permissions: Permission.READ,
|
permissions: Permission.READ,
|
||||||
}),
|
}),
|
||||||
contents: contents.map((r) => davResultToNode(r)),
|
contents: contents.map((r) => davResultToNode(r)).filter(filterHidden),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
apps/files/src/store/index.ts
Normal file
25
apps/files/src/store/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
|
||||||
|
*
|
||||||
|
* @author Ferdinand Thiessen <opensource@fthiessen.de>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
export const pinia = createPinia()
|
||||||
|
|
@ -214,6 +214,8 @@ export default defineComponent({
|
||||||
loading: true,
|
loading: true,
|
||||||
promise: null,
|
promise: null,
|
||||||
Type,
|
Type,
|
||||||
|
|
||||||
|
_unsubscribeStore: () => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -454,10 +456,16 @@ export default defineComponent({
|
||||||
subscribe('files:node:updated', this.onUpdatedNode)
|
subscribe('files:node:updated', this.onUpdatedNode)
|
||||||
subscribe('nextcloud:unified-search.search', this.onSearch)
|
subscribe('nextcloud:unified-search.search', this.onSearch)
|
||||||
subscribe('nextcloud:unified-search.reset', this.onSearch)
|
subscribe('nextcloud:unified-search.reset', this.onSearch)
|
||||||
|
|
||||||
|
// reload on settings change
|
||||||
|
this._unsubscribeStore = this.userConfigStore.$subscribe(() => this.fetchContent(), { deep: true })
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted() {
|
unmounted() {
|
||||||
unsubscribe('files:node:updated', this.onUpdatedNode)
|
unsubscribe('files:node:updated', this.onUpdatedNode)
|
||||||
|
unsubscribe('nextcloud:unified-search.search', this.onSearch)
|
||||||
|
unsubscribe('nextcloud:unified-search.reset', this.onSearch)
|
||||||
|
this._unsubscribeStore()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -20,29 +20,35 @@
|
||||||
-
|
-
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<NcAppSettingsDialog :open="open"
|
<NcAppSettingsDialog data-cy-files-navigation-settings
|
||||||
|
:open="open"
|
||||||
:show-navigation="true"
|
:show-navigation="true"
|
||||||
:name="t('files', 'Files settings')"
|
:name="t('files', 'Files settings')"
|
||||||
@update:open="onClose">
|
@update:open="onClose">
|
||||||
<!-- Settings API-->
|
<!-- Settings API-->
|
||||||
<NcAppSettingsSection id="settings" :name="t('files', 'Files settings')">
|
<NcAppSettingsSection id="settings" :name="t('files', 'Files settings')">
|
||||||
<NcCheckboxRadioSwitch :checked="userConfig.sort_favorites_first"
|
<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_favorites_first"
|
||||||
|
:checked="userConfig.sort_favorites_first"
|
||||||
@update:checked="setConfig('sort_favorites_first', $event)">
|
@update:checked="setConfig('sort_favorites_first', $event)">
|
||||||
{{ t('files', 'Sort favorites first') }}
|
{{ t('files', 'Sort favorites first') }}
|
||||||
</NcCheckboxRadioSwitch>
|
</NcCheckboxRadioSwitch>
|
||||||
<NcCheckboxRadioSwitch :checked="userConfig.sort_folders_first"
|
<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_folders_first"
|
||||||
|
:checked="userConfig.sort_folders_first"
|
||||||
@update:checked="setConfig('sort_folders_first', $event)">
|
@update:checked="setConfig('sort_folders_first', $event)">
|
||||||
{{ t('files', 'Sort folders before files') }}
|
{{ t('files', 'Sort folders before files') }}
|
||||||
</NcCheckboxRadioSwitch>
|
</NcCheckboxRadioSwitch>
|
||||||
<NcCheckboxRadioSwitch :checked="userConfig.show_hidden"
|
<NcCheckboxRadioSwitch data-cy-files-settings-setting="show_hidden"
|
||||||
|
:checked="userConfig.show_hidden"
|
||||||
@update:checked="setConfig('show_hidden', $event)">
|
@update:checked="setConfig('show_hidden', $event)">
|
||||||
{{ t('files', 'Show hidden files') }}
|
{{ t('files', 'Show hidden files') }}
|
||||||
</NcCheckboxRadioSwitch>
|
</NcCheckboxRadioSwitch>
|
||||||
<NcCheckboxRadioSwitch :checked="userConfig.crop_image_previews"
|
<NcCheckboxRadioSwitch data-cy-files-settings-setting="crop_image_previews"
|
||||||
|
:checked="userConfig.crop_image_previews"
|
||||||
@update:checked="setConfig('crop_image_previews', $event)">
|
@update:checked="setConfig('crop_image_previews', $event)">
|
||||||
{{ t('files', 'Crop image previews') }}
|
{{ t('files', 'Crop image previews') }}
|
||||||
</NcCheckboxRadioSwitch>
|
</NcCheckboxRadioSwitch>
|
||||||
<NcCheckboxRadioSwitch v-if="enableGridView"
|
<NcCheckboxRadioSwitch v-if="enableGridView"
|
||||||
|
data-cy-files-settings-setting="grid_view"
|
||||||
:checked="userConfig.grid_view"
|
:checked="userConfig.grid_view"
|
||||||
@update:checked="setConfig('grid_view', $event)">
|
@update:checked="setConfig('grid_view', $event)">
|
||||||
{{ t('files', 'Enable the grid view') }}
|
{{ t('files', 'Enable the grid view') }}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
import { View, getNavigation } from '@nextcloud/files'
|
||||||
import { translate as t } from '@nextcloud/l10n'
|
import { translate as t } from '@nextcloud/l10n'
|
||||||
import HistorySvg from '@mdi/svg/svg/history.svg?raw'
|
import HistorySvg from '@mdi/svg/svg/history.svg?raw'
|
||||||
|
|
||||||
import { getContents } from '../services/Recent'
|
import { getContents } from '../services/Recent'
|
||||||
import { View, getNavigation } from '@nextcloud/files'
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const Navigation = getNavigation()
|
const Navigation = getNavigation()
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ export const getActionsForFile = (filename: string) => getRowForFile(filename).f
|
||||||
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).find('button[aria-label="Actions"]')
|
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).find('button[aria-label="Actions"]')
|
||||||
|
|
||||||
export const triggerActionForFile = (filename: string, actionId: string) => {
|
export const triggerActionForFile = (filename: string, actionId: string) => {
|
||||||
getActionButtonForFile(filename).click()
|
getActionButtonForFile(filename).click({ force: true })
|
||||||
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click()
|
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('be.visible').click({ force: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const moveFile = (fileName: string, dirPath: string) => {
|
export const moveFile = (fileName: string, dirPath: string) => {
|
||||||
|
|
|
||||||
97
cypress/e2e/files/files-settings.cy.ts
Normal file
97
cypress/e2e/files/files-settings.cy.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
|
||||||
|
*
|
||||||
|
* @author Ferdinand Thiessen <opensource@fthiessen.de>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { User } from '@nextcloud/cypress'
|
||||||
|
import { getRowForFile } from './FilesUtils'
|
||||||
|
|
||||||
|
const showHiddenFiles = () => {
|
||||||
|
// Open the files settings
|
||||||
|
cy.get('[data-cy-files-navigation-settings-button] a').click({ force: true })
|
||||||
|
// Toggle the hidden files setting
|
||||||
|
cy.get('[data-cy-files-settings-setting="show_hidden"]').within(() => {
|
||||||
|
cy.get('input').should('not.be.checked')
|
||||||
|
cy.get('input').check({ force: true })
|
||||||
|
})
|
||||||
|
// Close the dialog
|
||||||
|
cy.get('[data-cy-files-navigation-settings] button[aria-label="Close"]').click()
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('files: Hide or show hidden files', { testIsolation: true }, () => {
|
||||||
|
let user: User
|
||||||
|
|
||||||
|
const setupFiles = () => cy.createRandomUser().then(($user) => {
|
||||||
|
user = $user
|
||||||
|
|
||||||
|
cy.uploadContent(user, new Blob([]), 'text/plain', '/.file')
|
||||||
|
cy.uploadContent(user, new Blob([]), 'text/plain', '/visible-file')
|
||||||
|
cy.mkdir(user, '/.folder')
|
||||||
|
cy.login(user)
|
||||||
|
})
|
||||||
|
|
||||||
|
context('view: All files', { testIsolation: false }, () => {
|
||||||
|
before(setupFiles)
|
||||||
|
|
||||||
|
it('hides dot-files by default', () => {
|
||||||
|
cy.visit('/apps/files')
|
||||||
|
|
||||||
|
getRowForFile('visible-file').should('be.visible')
|
||||||
|
getRowForFile('.file').should('not.exist')
|
||||||
|
getRowForFile('.folder').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can show hidden files', () => {
|
||||||
|
showHiddenFiles()
|
||||||
|
// Now the files should be visible
|
||||||
|
getRowForFile('.file').should('be.visible')
|
||||||
|
getRowForFile('.folder').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
context('view: Recent files', { testIsolation: false }, () => {
|
||||||
|
before(() => {
|
||||||
|
setupFiles().then(() => {
|
||||||
|
// also add hidden file in hidden folder
|
||||||
|
cy.uploadContent(user, new Blob([]), 'text/plain', '/.folder/other-file')
|
||||||
|
cy.login(user)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides dot-files by default', () => {
|
||||||
|
cy.visit('/apps/files/recent')
|
||||||
|
|
||||||
|
getRowForFile('visible-file').should('be.visible')
|
||||||
|
getRowForFile('.file').should('not.exist')
|
||||||
|
getRowForFile('.folder').should('not.exist')
|
||||||
|
getRowForFile('other-file').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can show hidden files', () => {
|
||||||
|
showHiddenFiles()
|
||||||
|
|
||||||
|
getRowForFile('visible-file').should('be.visible')
|
||||||
|
// Now the files should be visible
|
||||||
|
getRowForFile('.file').should('be.visible')
|
||||||
|
getRowForFile('.folder').should('be.visible')
|
||||||
|
getRowForFile('other-file').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue