Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
06fd580
feat: initial vault mode implementation
mzner Mar 3, 2026
39cd8a9
feat: get protected spaces
LukasHirt Mar 3, 2026
6730518
feat: add protected personal space type
LukasHirt Mar 4, 2026
263e778
feat: fetch and show vault spaces
mzner Mar 5, 2026
826c11c
fix: broken routes and tests for vault mode
mzner Mar 9, 2026
7f964b0
chore: add test for rebuild function
mzner Mar 12, 2026
4cf1365
feat: append vault to link when in safe mode
mzner Mar 19, 2026
406d400
refactor: remove protected-project and protected-personal as per back…
mzner Mar 20, 2026
e140636
fix(web-runtime): move mode switch dropdown to left side
mzner Mar 20, 2026
cccf7fc
feat(web-pkg): copying between vault and default mode
mzner Mar 23, 2026
ecbfcc9
feat: trigger mfa when user switches to vault mode
mzner Mar 9, 2026
f4b6e94
feat: show correct modal when user clicks on 3 dots menu to trigger s…
mzner Mar 16, 2026
0e2c9ea
feat(web-pkg): [OCISDEV-853] add vault:true token to search in vault …
mzner May 11, 2026
42b7ed6
chore(web-runtime): improve dropdown ui
mzner May 19, 2026
c4492b0
feat: [OCISDEV-527] check vault permission (#13802)
LukasHirt May 21, 2026
99d516e
feat(web-runtime): [OCISDEV-534] add MFA session expiry warning
mzner May 21, 2026
452d228
feat: show safe-personal and safe-spaces when using vault
mzner Mar 9, 2026
2ab87c3
feat: add vault and drive to breadcrumbs
mzner Mar 13, 2026
f065999
feat: add new theme colors
LukasHirt May 19, 2026
877412e
feat: add filter to type
mzner May 22, 2026
d4c2b7e
chore: check if vault capability is enabled
mzner May 22, 2026
05be282
fix: base MFA expiry timer on last request time instead of auth_time
mzner May 22, 2026
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
11 changes: 11 additions & 0 deletions changelog/unreleased/enhancement-add-new-theme-colors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Enhancement: Add new theme colors

We've added new theme colors. These new colors are:

- background-sidebar
- search-input-text-default
- search-input-text-muted
- search-input-border
- search-input-bg

https://github.com/owncloud/web/pull/13795
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-add-vault-to-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Add vault search separation

We've implemented vault search separation by adding the `vault:true` query token to the `<oc:pattern>` search payload. The token is now included in both "All files" and "Current folder" search requests, ensuring vault content is correctly scoped in all search scenarios.

https://github.com/owncloud/web/pull/13769
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-check-vault-permission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Check vault permission

When the user has the `VaultMode.ReadWriteEnabled.own` permission, the mode switch will be shown in the topbar.

https://github.com/owncloud/web/pull/13802
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: MFA session expiry warning

We've added a warning modal that notifies users before their multi-factor authentication session expires. Users can extend the session via silent OIDC renewal or dismiss the warning. The modal state is synchronized across multiple browser tabs using a BroadcastChannel.

https://github.com/owncloud/web/pull/13803
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Show correct modal for saveAs and open actions

We've added logic to show the correct modal when the user clicks on "Save As" or "Open" from the 3 dots context menu.

https://github.com/owncloud/web/pull/13759
5 changes: 5 additions & 0 deletions changelog/unreleased/enhancement-vault-aware-breadcrumbs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Vault-aware breadcrumbs

We've introduced vault-aware breadcrumbs that show Vault or Drive as the root item depending on the active scope. Users without vault access see the original labels instead.

https://github.com/owncloud/web/pull/13803
22 changes: 20 additions & 2 deletions packages/design-system/src/components/OcButton/OcButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,31 @@ const handlers = computed(() => {
.oc-icon > svg {
fill: $color;
}

&:focus:not([disabled]),
&:hover:not([disabled]) {
color: $hover-color;

.oc-icon > svg {
fill: $hover-color;
}
}
}
&-raw-inverse {
color: $contrast-color;

.oc-icon > svg {
fill: $contrast-color;
}

&:focus:not([disabled]),
&:hover:not([disabled]) {
color: $contrast-color;

.oc-icon > svg {
fill: $contrast-color;
}
}
}

&-filled {
Expand Down Expand Up @@ -340,12 +358,12 @@ const handlers = computed(() => {
&-outline {
&:focus:not([disabled]),
&:hover:not([disabled]) {
color: var(--oc-color-swatch-passive-default);
color: var(--oc-color-swatch-passive-contrast);
background-color: var(--oc-color-swatch-passive-hover-outline);
border-color: var(--oc-color-swatch-passive-hover-outline);

.oc-icon > svg {
fill: var(--oc-color-swatch-passive-default);
fill: var(--oc-color-swatch-passive-contrast);
}
}
}
Expand Down
56 changes: 44 additions & 12 deletions packages/web-app-files/src/components/AppBar/SharesNavigation.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<nav id="shares-navigation" class="oc-py-s" :aria-label="$gettext('Shares pages navigation')">
<oc-list class="oc-flex oc-visible@s">
<li v-for="navItem in navItems" :key="`shares-navigation-desktop-${navItem.to}`">
<li v-for="navItem in navItems" :key="`shares-navigation-desktop-${navItem.id}`">
<oc-button
type="router-link"
class="oc-mr-m oc-py-s shares-nav-desktop"
Expand All @@ -20,7 +20,7 @@
</oc-button>
<oc-drop toggle="#shares_navigation_mobile" mode="click" close-on-click padding-size="small">
<oc-list>
<li v-for="navItem in navItems" :key="`shares-navigation-mobile-${navItem.to}`">
<li v-for="navItem in navItems" :key="`shares-navigation-mobile-${navItem.id}`">
<oc-button
type="router-link"
class="oc-my-xs shares-nav-mobile"
Expand Down Expand Up @@ -53,16 +53,45 @@
import { useRouter } from '@ownclouders/web-pkg'
import { useActiveLocation } from '@ownclouders/web-pkg'
import { useGettext } from 'vue3-gettext'
import { RouteRecordNormalized } from 'vue-router'
import { RouteLocationRaw } from 'vue-router'

const { $gettext } = useGettext()
const router = useRouter()
const sharesRoutes = [locationSharesWithMe, locationSharesWithOthers, locationSharesViaLink].reduce<
Record<string, RouteRecordNormalized>
>((routes, route) => {
routes[route.name as string] = router.getRoutes().find((r) => r.name === route.name)
return routes
}, {})

const resolveScopeTemplatePath = (path: string, scopePrefix: string) =>
path.replace(/^\/:scope\(vault\)\?/, scopePrefix)

const locationToPath = (location: RouteLocationRaw) => {
const scope = unref(router.currentRoute).params?.scope
const scopePrefix = scope === 'vault' ? '/vault' : ''

if (typeof location === 'string') {
return location
}

const locationWithScope: RouteLocationRaw = {
...location,
...(scope && {
params: {
...((location as { params?: Record<string, unknown> }).params || {}),

Check warning on line 76 in packages/web-app-files/src/components/AppBar/SharesNavigation.vue

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The empty object is useless.

See more on https://sonarcloud.io/project/issues?id=owncloud_web&issues=AZ5O3To3qZzNMdwc8umN&open=AZ5O3To3qZzNMdwc8umN&pullRequest=13806
scope
}
})
}

const resolvedPath = router.resolve(locationWithScope).path
if (resolvedPath) {
return resolvedPath
}

const routeName = (location as { name?: string }).name
const route = routeName ? router.getRoutes().find((r) => r.name === routeName) : undefined
if (route?.path) {
return resolveScopeTemplatePath(route.path, scopePrefix)
}

return ''
}
const sharesWithMeActive = useActiveLocation(
isLocationSharesActive,
locationSharesWithMe.name as RouteShareTypes
Expand All @@ -77,20 +106,23 @@
)
const navItems = computed(() => [
{
id: locationSharesWithMe.name as string,
icon: 'share-forward',
to: sharesRoutes[locationSharesWithMe.name as string].path,
to: locationToPath(locationSharesWithMe),
text: $gettext('Shared with me'),
active: unref(sharesWithMeActive)
},
{
id: locationSharesWithOthers.name as string,
icon: 'reply',
to: sharesRoutes[locationSharesWithOthers.name as string].path,
to: locationToPath(locationSharesWithOthers),
text: $gettext('Shared with others'),
active: unref(sharesWithOthersActive)
},
{
id: locationSharesViaLink.name as string,
icon: 'link',
to: sharesRoutes[locationSharesViaLink.name as string].path,
to: locationToPath(locationSharesViaLink),
text: $gettext('Shared via link'),
active: unref(sharesViaLinkActive)
}
Expand Down
3 changes: 2 additions & 1 deletion packages/web-app-files/src/components/Search/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ const doSearch = (manuallyUpdateFilterChip = false) => {
lastModified,
mediaType,
scope: queryItemAsString(unref(scopeQuery)),
useScope: unref(doUseScope) === 'true'
useScope: unref(doUseScope) === 'true',
isVault: unref(route).params?.scope === 'vault'
})

const updateFilter = (v: Ref<InstanceType<typeof ItemFilter>>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,8 @@ watch(

.details-preview,
.details-icon-wrapper {
background-color: var(--oc-color-background-muted);
border: 10px solid var(--oc-color-background-muted);
background-color: var(--oc-color-background-highlight);
border: 10px solid var(--oc-color-background-highlight);
height: 230px;

background-size: contain;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
</p>
</div>
<template v-else>
<space-members v-if="showSpaceMembers" class="oc-background-highlight oc-p-m oc-mb-s" />
<file-shares v-else class="oc-background-highlight oc-p-m oc-mb-s" />
<file-links v-if="showLinks" class="oc-background-highlight oc-p-m" />
<space-members v-if="showSpaceMembers" class="oc-background-muted oc-p-m oc-mb-s" />
<file-shares v-else class="oc-background-muted oc-p-m oc-mb-s" />
<file-links v-if="showLinks" class="oc-background-muted oc-p-m" />
</template>
</div>
</template>
Expand Down
6 changes: 4 additions & 2 deletions packages/web-app-files/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
}

export const navItems = (context: ComponentCustomProperties): AppNavigationItem[] => {
const currentPath = window.location.pathname

Check warning on line 43 in packages/web-app-files/src/index.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=owncloud_web&issues=AZ5O3TtMqZzNMdwc8umO&open=AZ5O3TtMqZzNMdwc8umO&pullRequest=13806
const isVault = currentPath.startsWith('/vault')
const spacesStores = useSpacesStore()
const userStore = useUserStore()
const capabilityStore = useCapabilityStore()
Expand All @@ -48,7 +50,7 @@
return [
{
name() {
return $gettext('Personal')
return isVault ? $gettext('Safe-Personal') : $gettext('Personal')
},
icon: appInfo.icon,
route: {
Expand Down Expand Up @@ -101,7 +103,7 @@
priority: 30
},
{
name: $gettext('Spaces'),
name: isVault ? $gettext('Safe-Spaces') : $gettext('Spaces'),
icon: 'layout-grid',
route: {
path: `/${appInfo.id}/spaces/projects`
Expand Down
33 changes: 31 additions & 2 deletions packages/web-app-files/src/views/spaces/GenericSpace.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import {
import {
ResourceTransfer,
TransferType,
useAbility,
useConfigStore,
useExtensionRegistry,
useFileActions,
Expand Down Expand Up @@ -158,7 +159,8 @@ import {
useKeyboardActions,
useRoute,
useRouteQuery,
FolderLoaderOptions
FolderLoaderOptions,
useCapabilityStore
} from '@ownclouders/web-pkg'
import CreateAndUpload from '../../components/AppBar/CreateAndUpload.vue'
import FilesViewWrapper from '../../components/FilesViewWrapper.vue'
Expand Down Expand Up @@ -189,6 +191,8 @@ interface Props {
const { space = null, item = null, itemId = null } = defineProps<Props>()

const router = useRouter()
const { can } = useAbility()
const capabilityStore = useCapabilityStore()
const userStore = useUserStore()
const { $gettext, $ngettext } = useGettext()
const openWithDefaultAppQuery = useRouteQuery('openWithDefaultApp')
Expand Down Expand Up @@ -256,12 +260,26 @@ const titleSegments = computed(() => {
useDocumentTitle({ titleSegments })

const route = useRoute()
const canAccessVault = computed(() => capabilityStore.vaultEnabled && can('read-all', 'Vault'))

const getSpacesBreadcrumbText = () => {
if (!unref(canAccessVault)) {
return $gettext('Spaces')
}

if (unref(route).params.scope === 'vault') {
return $gettext('Vault')
}

return $gettext('Drive')
}

const breadcrumbs = computed(() => {
const rootBreadcrumbItems: BreadcrumbItem[] = []
if (isProjectSpaceResource(unref(space))) {
rootBreadcrumbItems.push({
id: uuidV4(),
text: $gettext('Spaces'),
text: getSpacesBreadcrumbText(),
to: createLocationSpaces('files-spaces-projects'),
isStaticNav: true
})
Expand All @@ -286,6 +304,17 @@ const breadcrumbs = computed(() => {
let { params, query } = createFileRouteOptions(unref(space), { fileId: unref(space).fileId })
query = omit({ ...unref(route).query, ...query }, 'page')
if (isPersonalSpaceResource(unref(space))) {
if (unref(canAccessVault)) {
const vaultText =
unref(route).params.scope === 'vault' ? $gettext('Vault') : $gettext('Drive')
rootBreadcrumbItems.push({
id: uuidV4(),
text: vaultText,
to: createLocationSpaces('files-spaces-projects'),
isStaticNav: true
})
}

spaceBreadcrumbItem = {
id: uuidV4(),
text: unref(space).name,
Expand Down
24 changes: 20 additions & 4 deletions packages/web-app-files/src/views/spaces/Projects.vue
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ import {
useRoute,
Pagination,
FileSideBar,
NoContentMessage
NoContentMessage,
useCapabilityStore
} from '@ownclouders/web-pkg'
import SpaceContextActions from '../../components/Spaces/SpaceContextActions.vue'
import {
Expand Down Expand Up @@ -221,6 +222,7 @@ const { $gettext } = language
const filterTerm = ref('')
const markInstance = ref(undefined)
const includeDisabledParam = useRouteQuery('q_includeDisabled')
const capabilityStore = useCapabilityStore()

const { setSelection, initResourceList, clearResourceList, setAncestorMetaData } =
useResourcesStore()
Expand All @@ -230,7 +232,8 @@ const loadResourcesTask = useTask(function* (signal) {
setAncestorMetaData({})
yield spacesStore.reloadProjectSpaces({
graphClient: clientService.graphAuthenticated,
signal
signal,
isInVault: unref(route)?.params?.scope === 'vault'
})
initResourceList({ currentFolder: null, resources: unref(spaces) })
})
Expand All @@ -248,7 +251,7 @@ let loadPreviewToken: string = null
const { isSideBarOpen, sideBarActivePanel } = useSideBar()

const runtimeSpaces = computed(() => {
return spacesStore.spaces.filter(isProjectSpaceResource) || []
return spacesStore.spaces.filter((space) => isProjectSpaceResource(space)) || []
})
const selectedSpace = computed(() => {
if (
Expand Down Expand Up @@ -334,6 +337,7 @@ watch(filterTerm, async () => {
})

const hasCreatePermission = computed(() => can('create-all', 'Drive'))
const canAccessVault = computed(() => capabilityStore.vaultEnabled && can('read-all', 'Vault'))

const extensionRegistry = useExtensionRegistry()
const viewModes = computed(() => {
Expand Down Expand Up @@ -458,10 +462,22 @@ const spacesHelpList = computed(() => {
}
]
})
const getBreadcrumbText = () => {
if (!unref(canAccessVault)) {
return $gettext('Spaces')
}

if (unref(route).params.scope === 'vault') {
return $gettext('Vault')
}

return $gettext('Drive')
}

const breadcrumbs = computed(() => {
return [
{
text: $gettext('Spaces'),
text: getBreadcrumbText(),
onClick: () => loadResourcesTask.perform(),
isStativNav: true
}
Expand Down
Loading
Loading