diff --git a/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue b/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue index 1e7ceecbe70..459e261181c 100644 --- a/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue +++ b/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue @@ -194,19 +194,31 @@ export default defineComponent({ type: Object, required: false, default: null + }, + paginationPages: { + type: Number, + required: false, + default: null + }, + paginationPage: { + type: Number, + required: false, + default: null } }, - setup() { + setup(props) { const capabilityStore = useCapabilityStore() const configStore = useConfigStore() const { getMatchingSpace } = useGetMatchingSpace() const { loadPreview } = useLoadPreview() - const { paginationPages, paginationPage } = useResourcesViewDefaults< - IncomingShareResource, - any, - any[] - >() + const { + paginationPages: defaultPaginationPages, + paginationPage: defaultPaginationPage + } = useResourcesViewDefaults() + + const paginationPages = computed(() => props.paginationPages ?? unref(defaultPaginationPages)) + const paginationPage = computed(() => props.paginationPage ?? unref(defaultPaginationPage)) const { triggerDefaultAction } = useFileActions() const { actions: hideShareActions } = useFileActionsToggleHideShare() diff --git a/packages/web-app-files/src/views/shares/SharedWithMe.vue b/packages/web-app-files/src/views/shares/SharedWithMe.vue index f49517ccf00..90b26a5942d 100644 --- a/packages/web-app-files/src/views/shares/SharedWithMe.vue +++ b/packages/web-app-files/src/views/shares/SharedWithMe.vue @@ -80,6 +80,8 @@ areHiddenFilesShown ? $gettext('No hidden shares') : $gettext('No shares') " :grouping-settings="groupingSettings" + :pagination-pages="paginationPages" + :pagination-page="paginationPage" /> @@ -101,13 +103,14 @@ import { FileSideBar, InlineFilterOption, ItemFilter, + usePagination, useAppsStore, useResourcesStore } from '@ownclouders/web-pkg' import { AppBar, ItemFilterInline } from '@ownclouders/web-pkg' -import { queryItemAsString, useRouteQuery } from '@ownclouders/web-pkg' +import { queryItemAsString, useRouteQuery, useRouter } from '@ownclouders/web-pkg' import SharedWithMeSection from '../../components/Shares/SharedWithMeSection.vue' -import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue' +import { computed, defineComponent, onMounted, ref, unref, watch, nextTick } from 'vue' import FilesViewWrapper from '../../components/FilesViewWrapper.vue' import { useGetMatchingSpace, useSort } from '@ownclouders/web-pkg' import { useGroupingSettings } from '@ownclouders/web-pkg' @@ -134,6 +137,8 @@ export default defineComponent({ const appsStore = useAppsStore() const resourcesStore = useResourcesStore() + const router = useRouter() + const { areResourcesLoading, sortFields, @@ -143,10 +148,14 @@ export default defineComponent({ selectedResourcesIds, sideBarActivePanel, isSideBarOpen, - paginatedResources, scrollToResourceFromRoute } = useResourcesViewDefaults() + // Use all active resources as the base — filtering must happen before pagination + const allResources = computed( + () => resourcesStore.activeResources as unknown as IncomingShareResource[] + ) + const { $gettext } = useGettext() const areHiddenFilesShown = ref(false) @@ -167,8 +176,8 @@ export default defineComponent({ resourcesStore.resetSelection() } - const visibleShares = computed(() => unref(paginatedResources).filter((r) => !r.hidden)) - const hiddenShares = computed(() => unref(paginatedResources).filter((r) => r.hidden)) + const visibleShares = computed(() => unref(allResources).filter((r) => !r.hidden)) + const hiddenShares = computed(() => unref(allResources).filter((r) => r.hidden)) const currentItems = computed(() => { return unref(areHiddenFilesShown) ? unref(hiddenShares) : unref(visibleShares) }) @@ -217,11 +226,31 @@ export default defineComponent({ } }) - const { sortBy, sortDir, items, handleSort } = useSort({ + const { sortBy, sortDir, items: sortedFilteredItems, handleSort } = useSort({ items: filteredItems, fields: sortFields }) + const { + items: paginatedFilteredItems, + total: paginationPages, + page: paginationPage + } = usePagination({ items: sortedFilteredItems, perPageStoragePrefix: 'files' }) + + // Reset to page 1 whenever the active filter set changes to avoid landing on an empty page + watch( + [selectedShareTypesQuery, selectedSharedByQuery, filterTerm, areHiddenFilesShown], + async () => { + await nextTick() + if (unref(paginationPage) > unref(paginationPages)) { + router.push({ query: { ...router.currentRoute.value.query, page: 1 } }) + } + } + ) + + // Keep items alias for backward compat (e.g. scrollToResourceFromRoute) + const items = paginatedFilteredItems + const { getMatchingSpace } = useGetMatchingSpace() const selectedShareSpace = computed(() => { @@ -245,7 +274,7 @@ export default defineComponent({ } const shareTypes = computed(() => { - const uniqueShareTypes = uniq(unref(paginatedResources).flatMap((i) => i.shareTypes)) + const uniqueShareTypes = uniq(unref(allResources).flatMap((i) => i.shareTypes)) const ocmAvailable = appsStore.appIds.includes('open-cloud-mesh') if (ocmAvailable && !uniqueShareTypes.includes(ShareTypes.remote.value)) { @@ -262,10 +291,25 @@ export default defineComponent({ }) const fileOwners = computed(() => { - const flatList = unref(paginatedResources) + const flatList = unref(allResources) .map((i) => i.sharedBy) .flat() - return [...new Map(flatList.map((item) => [item.displayName, item])).values()] + // Deduplicate by id so users with the same display name are not merged + const uniqueById = [...new Map(flatList.map((item) => [item.id, item])).values()] + // Count how many distinct ids share the same display name + const nameCounts = uniqueById.reduce( + (acc, item) => { + acc[item.displayName] = (acc[item.displayName] || 0) + 1 + return acc + }, + {} as Record + ) + // Append the account id when two users share the same display name + return uniqueById.map((item) => + nameCounts[item.displayName] > 1 + ? { ...item, displayName: `${item.displayName} (${item.id})` } + : item + ) }) onMounted(() => { @@ -296,6 +340,8 @@ export default defineComponent({ sortBy, sortDir, items, + paginationPages, + paginationPage, // CERN ...useGroupingSettings({ sortBy: sortBy, sortDir: sortDir }) diff --git a/packages/web-app-files/src/views/spaces/DriveRedirect.vue b/packages/web-app-files/src/views/spaces/DriveRedirect.vue index 8ca255ef5bb..de15cec42b6 100644 --- a/packages/web-app-files/src/views/spaces/DriveRedirect.vue +++ b/packages/web-app-files/src/views/spaces/DriveRedirect.vue @@ -1,16 +1,17 @@ diff --git a/packages/web-app-files/tests/unit/views/shares/SharedWithMe.spec.ts b/packages/web-app-files/tests/unit/views/shares/SharedWithMe.spec.ts index 552efa744f1..f954b64e2f4 100644 --- a/packages/web-app-files/tests/unit/views/shares/SharedWithMe.spec.ts +++ b/packages/web-app-files/tests/unit/views/shares/SharedWithMe.spec.ts @@ -20,6 +20,12 @@ vi.mock('../../../../src/composables/resourcesViewDefaults') vi.mock('@ownclouders/web-pkg', async (importOriginal) => ({ ...(await importOriginal()), useSort: vi.fn().mockImplementation(() => useSortMock()), + usePagination: vi.fn().mockImplementation(({ items }) => ({ + items, + total: ref(1), + page: ref(1), + perPage: ref(100) + })), queryItemAsString: vi.fn(), useRouteQuery: vi.fn(), useOpenWithDefaultApp: vi.fn() @@ -196,8 +202,11 @@ function getMountedWrapper({ mocks: defaultMocks, wrapper: mount(SharedWithMe, { global: { - plugins: [...defaultPlugins()], + plugins: [ + ...defaultPlugins({ piniaOptions: { resourcesStore: { resources: files } } }) + ], mocks: defaultMocks, + provide: defaultMocks, stubs: { ...defaultStubs, itemFilterInline: true, ItemFilter: true } } }) diff --git a/packages/web-client/src/helpers/resource/functions.ts b/packages/web-client/src/helpers/resource/functions.ts index b7756d1c56b..707a09c9952 100644 --- a/packages/web-client/src/helpers/resource/functions.ts +++ b/packages/web-client/src/helpers/resource/functions.ts @@ -87,7 +87,8 @@ const convertObjectToCamelCaseKeys = (data: Record) => { } export function buildResource(resource: WebDavResponseResource): Resource { - const name = resource.props[DavProperty.Name]?.toString() || basename(resource.filename) + const name = + resource.basename || resource.props[DavProperty.Name]?.toString() || basename(resource.filename) const id = resource.props[DavProperty.FileId] const isFolder = resource.type === 'directory' diff --git a/packages/web-pkg/src/helpers/router/routeOptions.ts b/packages/web-pkg/src/helpers/router/routeOptions.ts index 866d8a2257e..721546df4fb 100644 --- a/packages/web-pkg/src/helpers/router/routeOptions.ts +++ b/packages/web-pkg/src/helpers/router/routeOptions.ts @@ -23,7 +23,7 @@ export const createFileRouteOptions = ( const config = options?.configStore || useConfigStore() return { params: { - driveAliasAndItem: space.getDriveAliasAndItem({ path: target.path || '' } as Resource) + driveAliasAndItem: space?.getDriveAliasAndItem({ path: target.path || '' } as Resource) }, query: { ...(isShareSpaceResource(space) && { shareId: space.id }), diff --git a/packages/web-runtime/src/components/Topbar/TopBar.vue b/packages/web-runtime/src/components/Topbar/TopBar.vue index b2c27515f1e..b0ed7dfaae3 100644 --- a/packages/web-runtime/src/components/Topbar/TopBar.vue +++ b/packages/web-runtime/src/components/Topbar/TopBar.vue @@ -9,7 +9,12 @@ v-if="appMenuExtensions.length && !isEmbedModeEnabled && !hideAppSwitcher" :menu-items="appMenuExtensions" /> - + @@ -112,7 +117,7 @@ export default { } } - if (isEmbedModeEnabled) { + if (unref(isEmbedModeEnabled)) { const currentRoute = unref(router.currentRoute) return { name: currentRoute.name, diff --git a/packages/web-runtime/src/container/versions.ts b/packages/web-runtime/src/container/versions.ts index 9bd2c6dca86..6ff3dad41b5 100644 --- a/packages/web-runtime/src/container/versions.ts +++ b/packages/web-runtime/src/container/versions.ts @@ -1,7 +1,7 @@ import { CapabilityStore } from '@ownclouders/web-pkg' export const getWebVersion = (): string => { - return `ownCloud Web UI ${process.env.PACKAGE_VERSION}` + return `CERNBox Web ${process.env.PACKAGE_VERSION}` } export const getBackendVersion = ({ @@ -13,7 +13,7 @@ export const getBackendVersion = ({ if (!backendStatus || !backendStatus.versionstring) { return undefined } - const product = backendStatus.product || 'ownCloud' + const product = backendStatus.product || 'Reva' const version = backendStatus.productversion || backendStatus.versionstring const edition = backendStatus.edition return `${product} ${version} ${edition}` diff --git a/packages/web-runtime/tests/unit/container/versions.spec.ts b/packages/web-runtime/tests/unit/container/versions.spec.ts index 60aa2723361..6c8f0a174d0 100644 --- a/packages/web-runtime/tests/unit/container/versions.spec.ts +++ b/packages/web-runtime/tests/unit/container/versions.spec.ts @@ -9,7 +9,7 @@ describe('collect version information', () => { process.env.PACKAGE_VERSION = '4.7.0' }) it('provides the web version with a static string without exceptions', () => { - expect(getWebVersion()).toBe('ownCloud Web UI 4.7.0') + expect(getWebVersion()).toBe('CERNBox Web 4.7.0') }) }) describe('backend version', () => { @@ -29,7 +29,7 @@ describe('collect version information', () => { versionstring: '10.8.0', edition: 'Community' }) - expect(getBackendVersion({ capabilityStore })).toBe('ownCloud 10.8.0 Community') + expect(getBackendVersion({ capabilityStore })).toBe('Reva 10.8.0 Community') }) it('provides the backend version as concatenation of product, version and edition', () => { const capabilityStore = versionStore({