From 187e582a73b562d253d2980dd0e0a38b2ae46604 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Wed, 13 May 2026 15:03:11 +0200 Subject: [PATCH 1/5] Rename to CERNBox Web --- packages/web-runtime/src/container/versions.ts | 4 ++-- packages/web-runtime/tests/unit/container/versions.spec.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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({ From afb4d0f81ee6ed930d60a96f55d13b3098b49883 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Mon, 11 May 2026 09:55:17 +0200 Subject: [PATCH 2/5] Fix logo click Ensure that it redirects home if embed mode not set --- packages/web-runtime/src/components/Topbar/TopBar.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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, From da720394aef511c35fdbfad4e8881d62cac9bdf1 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Tue, 12 May 2026 17:35:00 +0200 Subject: [PATCH 3/5] Fix wrong filename on folders with name ending in .0 The .0 is being removed in the dav library. --- packages/web-client/src/helpers/resource/functions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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' From fbb6cc9029093327cecf6848c47568ed5ac9a885 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Wed, 13 May 2026 10:50:21 +0200 Subject: [PATCH 4/5] Show not found error if space is unkown --- .../src/views/spaces/DriveRedirect.vue | 63 +++++++++++-------- .../src/helpers/router/routeOptions.ts | 2 +- 2 files changed, 38 insertions(+), 27 deletions(-) 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-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 }), From 6627ba629ae825b42cb346ca9397fd868bb981c5 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Thu, 14 May 2026 10:36:18 +0200 Subject: [PATCH 5/5] Fix shared-with-me filter and pagination Filtering now operates on the full resource list before pagination, so page counts and page navigation always reflect the filtered result rather than the raw total. A watcher resets to page 1 whenever any filter changes to prevent landing on an empty page. The "Shared By" filter deduplicates by user id instead of display name, so two accounts with identical display names are no longer merged into one option. When two users share the same display name, the account id is appended in parentheses to distinguish them. SharedWithMeSection accepts optional paginationPages/paginationPage props so the parent can supply the correct filtered page count to the pagination control. --- .../components/Shares/SharedWithMeSection.vue | 24 +++++-- .../src/views/shares/SharedWithMe.vue | 64 ++++++++++++++++--- .../unit/views/shares/SharedWithMe.spec.ts | 11 +++- 3 files changed, 83 insertions(+), 16 deletions(-) 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/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 } } })