diff --git a/changelog/unreleased/change-drive-aliases-in-urls b/changelog/unreleased/change-drive-aliases-in-urls index 762a3114a79..10fa41b0bce 100644 --- a/changelog/unreleased/change-drive-aliases-in-urls +++ b/changelog/unreleased/change-drive-aliases-in-urls @@ -7,3 +7,4 @@ BREAKING CHANGE for developers: the appDefaults composables from web-pkg now wor https://github.com/owncloud/web/issues/6648 https://github.com/owncloud/web/pull/7430 +https://github.com/owncloud/web/pull/7791 diff --git a/packages/web-app-files/src/router/public.ts b/packages/web-app-files/src/router/public.ts index e3da92bcbe5..ab638976a90 100644 --- a/packages/web-app-files/src/router/public.ts +++ b/packages/web-app-files/src/router/public.ts @@ -29,7 +29,6 @@ export const buildRoutes = (components: RouteComponents): RouteConfig[] => [ component: components.Spaces.DriveResolver, meta: { auth: false, - title: $gettext('Public files'), patchCleanPath: true } } diff --git a/packages/web-app-files/src/router/spaces.ts b/packages/web-app-files/src/router/spaces.ts index e42955eb45f..3f84b9343b7 100644 --- a/packages/web-app-files/src/router/spaces.ts +++ b/packages/web-app-files/src/router/spaces.ts @@ -35,9 +35,7 @@ export const buildRoutes = (components: RouteComponents): RouteConfig[] => [ name: locationSpacesGeneric.name, component: components.Spaces.DriveResolver, meta: { - patchCleanPath: true, - // FIXME: we'd need to extract the title from the resolved space... - title: $gettext('Space') + patchCleanPath: true } } ] diff --git a/packages/web-app-files/src/router/trash.ts b/packages/web-app-files/src/router/trash.ts index d9caf3d9640..8b9daaf2e5a 100644 --- a/packages/web-app-files/src/router/trash.ts +++ b/packages/web-app-files/src/router/trash.ts @@ -1,6 +1,6 @@ import { RouteComponents } from './router' import { Location, RouteConfig } from 'vue-router' -import { createLocation, $gettext, isLocationActiveDirector } from './utils' +import { createLocation, isLocationActiveDirector } from './utils' type trashTypes = 'files-trash-generic' @@ -21,8 +21,7 @@ export const buildRoutes = (components: RouteComponents): RouteConfig[] => [ path: ':driveAliasAndItem*', component: components.Spaces.DriveResolver, meta: { - patchCleanPath: true, - title: $gettext('Deleted files') + patchCleanPath: true } } ] diff --git a/packages/web-app-files/src/views/spaces/GenericSpace.vue b/packages/web-app-files/src/views/spaces/GenericSpace.vue index 5a721024129..e2e721f3d1f 100644 --- a/packages/web-app-files/src/views/spaces/GenericSpace.vue +++ b/packages/web-app-files/src/views/spaces/GenericSpace.vue @@ -118,10 +118,10 @@ import { eventBus } from 'web-pkg/src/services/eventBus' import { BreadcrumbItem, breadcrumbsFromPath, concatBreadcrumbs } from '../../helpers/breadcrumbs' import { createLocationPublic, createLocationSpaces } from '../../router' import { useResourcesViewDefaults } from '../../composables' -import { computed, defineComponent, PropType } from '@vue/composition-api' +import { computed, defineComponent, PropType, unref } from '@vue/composition-api' import { ResourceTransfer, TransferType } from '../../helpers/resource' import { Resource } from 'web-client' -import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' +import { useRoute, useTranslations } from 'web-pkg/src/composables' import { Location } from 'vue-router' import { isPersonalSpaceResource, @@ -134,6 +134,8 @@ import { CreateTargetRouteOptions } from '../../helpers/folderLink' import { FolderLoaderOptions } from '../../services/folder' import { createFileRouteOptions } from 'web-pkg/src/helpers/router' import omit from 'lodash-es/omit' +import { useDocumentTitle } from 'web-pkg/src/composables/appDefaults/useDocumentTitle' +import { basename } from 'path' const visibilityObserver = new VisibilityObserver() @@ -189,72 +191,63 @@ export default defineComponent({ // for now the space header is only available in the root of a project space. return props.space.driveType === 'project' && props.item === '/' }) - return { - ...useResourcesViewDefaults(), - resourceTargetRouteCallback, - hasShareJail: useCapabilityShareJailEnabled(), - hasSpaceHeader - } - }, - computed: { - ...mapState(['app']), - ...mapState('Files', ['files']), - ...mapGetters('Files', [ - 'highlightedFile', - 'currentFolder', - 'totalFilesCount', - 'totalFilesSize' - ]), - ...mapGetters(['user', 'configuration']), + const titleSegments = computed(() => { + const segments = [props.space.name] + if (props.item !== '/') { + segments.unshift(basename(props.item)) + } - isEmpty() { - return this.paginatedResources.length < 1 - }, + return segments + }) + useDocumentTitle({ titleSegments }) - breadcrumbs() { + const { $gettext } = useTranslations() + const route = useRoute() + const breadcrumbs = computed(() => { + const space = props.space const rootBreadcrumbItems: BreadcrumbItem[] = [] - if (isProjectSpaceResource(this.space)) { + if (isProjectSpaceResource(space)) { rootBreadcrumbItems.push({ - text: this.$gettext('Spaces'), + text: $gettext('Spaces'), to: createLocationSpaces('files-spaces-projects') }) - } else if (isShareSpaceResource(this.space)) { + } else if (isShareSpaceResource(space)) { rootBreadcrumbItems.push( { - text: this.$gettext('Shares'), + text: $gettext('Shares'), to: { path: '/files/shares' } }, { - text: this.$gettext('Shared with me'), + text: $gettext('Shared with me'), to: { path: '/files/shares/with-me' } } ) } let spaceBreadcrumbItem - let { params, query } = createFileRouteOptions(this.space, { fileId: this.space.fileId }) - query = { ...this.$route.query, ...query } - if (isPersonalSpaceResource(this.space)) { + let { params, query } = createFileRouteOptions(space, { fileId: space.fileId }) + query = { ...unref(route).query, ...query } + if (isPersonalSpaceResource(space)) { spaceBreadcrumbItem = { - text: this.hasShareJail ? this.$gettext('Personal') : this.$gettext('All files'), + text: space.name, to: createLocationSpaces('files-spaces-generic', { params, query }) } - } else if (isShareSpaceResource(this.space)) { + } else if (isShareSpaceResource(space)) { spaceBreadcrumbItem = { allowContextActions: true, - text: this.space.name, + text: space.name, to: createLocationSpaces('files-spaces-generic', { params, query: omit(query, 'fileId') }) } - } else if (isPublicSpaceResource(this.space)) { + } else if (isPublicSpaceResource(space)) { spaceBreadcrumbItem = { - text: this.$gettext('Public link'), + text: $gettext('Public link'), to: createLocationPublic('files-public-link', { params, query @@ -262,8 +255,8 @@ export default defineComponent({ } } else { spaceBreadcrumbItem = { - allowContextActions: !this.hasSpaceHeader, - text: this.space.name, + allowContextActions: !unref(hasSpaceHeader), + text: space.name, to: createLocationSpaces('files-spaces-generic', { params, query @@ -275,8 +268,31 @@ export default defineComponent({ ...rootBreadcrumbItems, spaceBreadcrumbItem, // FIXME: needs file ids for each parent folder path - ...breadcrumbsFromPath(this.$route, this.item) + ...breadcrumbsFromPath(unref(route), props.item) ) + }) + + return { + ...useResourcesViewDefaults(), + resourceTargetRouteCallback, + breadcrumbs, + hasSpaceHeader + } + }, + + computed: { + ...mapState(['app']), + ...mapState('Files', ['files']), + ...mapGetters('Files', [ + 'highlightedFile', + 'currentFolder', + 'totalFilesCount', + 'totalFilesSize' + ]), + ...mapGetters(['user', 'configuration']), + + isEmpty() { + return this.paginatedResources.length < 1 }, folderNotFound() { diff --git a/packages/web-app-files/src/views/spaces/GenericTrash.vue b/packages/web-app-files/src/views/spaces/GenericTrash.vue index 33717e7ce4f..0624ce19b15 100644 --- a/packages/web-app-files/src/views/spaces/GenericTrash.vue +++ b/packages/web-app-files/src/views/spaces/GenericTrash.vue @@ -75,11 +75,16 @@ import NoContentMessage from 'web-pkg/src/components/NoContentMessage.vue' import { eventBus } from 'web-pkg/src/services/eventBus' import { useResourcesViewDefaults } from '../../composables' -import { computed, defineComponent, PropType } from '@vue/composition-api' +import { computed, defineComponent, PropType, unref } from '@vue/composition-api' import { Resource } from 'web-client' -import { useCapabilityShareJailEnabled, useTranslations } from 'web-pkg/src/composables' +import { + useCapabilityShareJailEnabled, + useCapabilitySpacesEnabled, + useTranslations +} from 'web-pkg/src/composables' import { createLocationTrash } from '../../router' import { isProjectSpaceResource, SpaceResource } from 'web-client/src/helpers' +import { useDocumentTitle } from 'web-pkg/src/composables/appDefaults/useDocumentTitle' export default defineComponent({ name: 'GenericTrash', @@ -116,6 +121,17 @@ export default defineComponent({ ? $gettext('You have no deleted files') : $gettext('Space has no deleted files') }) + + const hasSpaces = useCapabilitySpacesEnabled() + const titleSegments = computed(() => { + const segments = [$gettext('Deleted files')] + if (unref(hasSpaces)) { + segments.unshift(props.space.name) + } + return segments + }) + useDocumentTitle({ titleSegments }) + return { ...useResourcesViewDefaults(), hasShareJail: useCapabilityShareJailEnabled(), diff --git a/packages/web-pkg/src/composables/appDefaults/useAppDocumentTitle.ts b/packages/web-pkg/src/composables/appDefaults/useAppDocumentTitle.ts index 893967a7c23..5f7e71d05f7 100644 --- a/packages/web-pkg/src/composables/appDefaults/useAppDocumentTitle.ts +++ b/packages/web-pkg/src/composables/appDefaults/useAppDocumentTitle.ts @@ -23,21 +23,14 @@ export function useAppDocumentTitle({ }: AppDocumentTitleOptions): void { const appMeta = useAppMeta({ applicationId, store }) - const title = computed(() => { + const titleSegments = computed(() => { const fileName = basename(unref(unref(currentFileContext).fileName)) const meta = unref(unref(appMeta).applicationMeta) - return [ - fileName, - unref(applicationName) || meta.name || meta.id, - store.getters.configuration.currentTheme.general.name - ] - .filter(Boolean) - .join(' - ') + return [fileName, unref(applicationName) || meta.name || meta.id].filter(Boolean) }) useDocumentTitle({ - document, - title + titleSegments }) } diff --git a/packages/web-pkg/src/composables/appDefaults/useDocumentTitle.ts b/packages/web-pkg/src/composables/appDefaults/useDocumentTitle.ts index d22916bbbb1..bb5695f8288 100755 --- a/packages/web-pkg/src/composables/appDefaults/useDocumentTitle.ts +++ b/packages/web-pkg/src/composables/appDefaults/useDocumentTitle.ts @@ -1,15 +1,32 @@ -import { watch, Ref } from '@vue/composition-api' +import { watch, Ref, unref } from '@vue/composition-api' +import { useStore } from '../store' +import { Store } from 'vuex' +import { useEventBus } from '../eventBus' +import { EventBus } from '../../services' interface DocumentTitleOptions { - document: Document - title: Ref + titleSegments: Ref + store?: Store + eventBus?: EventBus } -export function useDocumentTitle({ document, title }: DocumentTitleOptions): void { +export function useDocumentTitle({ titleSegments, store, eventBus }: DocumentTitleOptions): void { + store = store || useStore() + eventBus = eventBus || useEventBus() + watch( - title, - (newTitle) => { - document.title = newTitle + titleSegments, + (newTitleSegments) => { + const titleSegments = unref(newTitleSegments) + + const glue = ' - ' + const generalName = store.getters['configuration'].currentTheme.general.name + const payload = { + shortDocumentTitle: titleSegments.join(glue), + fullDocumentTitle: [...titleSegments, generalName].join(glue) + } + + eventBus.publish('runtime.documentTitle.changed', payload) }, { immediate: true } ) diff --git a/packages/web-pkg/src/composables/eventBus/index.ts b/packages/web-pkg/src/composables/eventBus/index.ts new file mode 100644 index 00000000000..dbad76fb367 --- /dev/null +++ b/packages/web-pkg/src/composables/eventBus/index.ts @@ -0,0 +1 @@ +export * from './useEventBus' diff --git a/packages/web-pkg/src/composables/eventBus/useEventBus.ts b/packages/web-pkg/src/composables/eventBus/useEventBus.ts new file mode 100644 index 00000000000..d92424f8a28 --- /dev/null +++ b/packages/web-pkg/src/composables/eventBus/useEventBus.ts @@ -0,0 +1,5 @@ +import { eventBus } from '../../services' + +export const useEventBus = () => { + return eventBus +} diff --git a/packages/web-runtime/src/App.vue b/packages/web-runtime/src/App.vue index f84a3ceadf2..adaf7941e5d 100644 --- a/packages/web-runtime/src/App.vue +++ b/packages/web-runtime/src/App.vue @@ -51,6 +51,7 @@ import { getBackendVersion, getWebVersion } from './container/versions' import { defineComponent } from '@vue/composition-api' import { isPublicLinkContext, isUserContext, isAuthenticationRequired } from './router' import { additionalTranslations } from './helpers/additionalTranslations' // eslint-disable-line +import { eventBus } from 'web-pkg/src/services' export default defineComponent({ components: { @@ -100,8 +101,13 @@ export default defineComponent({ $route: { immediate: true, handler: function (to) { - this.announceRouteChange(to) - document.title = this.extractPageTitleFromRoute(to) + const extracted = this.extractPageTitleFromRoute(to) + if (!extracted) { + return + } + const { shortDocumentTitle, fullDocumentTitle } = extracted + this.announceRouteChange(shortDocumentTitle) + document.title = fullDocumentTitle } }, capabilities: { @@ -141,7 +147,15 @@ export default defineComponent({ } } }, - + mounted() { + eventBus.subscribe( + 'runtime.documentTitle.changed', + ({ shortDocumentTitle, fullDocumentTitle }) => { + document.title = fullDocumentTitle + this.announceRouteChange(shortDocumentTitle) + } + ) + }, destroyed() { if (this.$_notificationsInterval) { clearInterval(this.$_notificationsInterval) @@ -179,33 +193,23 @@ export default defineComponent({ }) }, - announceRouteChange(route) { - const pageTitle = this.extractPageTitleFromRoute(route, false) + announceRouteChange(pageTitle) { const translated = this.$gettext('Navigated to %{ pageTitle }') this.announcement = this.$gettextInterpolate(translated, { pageTitle }) }, - extractPageTitleFromRoute(route, includeGeneralName = true) { - const routeTitle = route.meta.title ? this.$gettext(route.meta.title) : route.name - const titleSegments = [routeTitle] - - if (includeGeneralName) { - titleSegments.push(this.configuration.currentTheme.general.name) + extractPageTitleFromRoute(route) { + const routeTitle = route.meta.title ? this.$gettext(route.meta.title) : undefined + if (!routeTitle) { + return } - - if (route.params.item) { - if (route.name.startsWith('files-')) { - const fileTree = route.params.item.split('/').filter((el) => el.length) - - if (fileTree.length) { - titleSegments.unshift(fileTree.pop()) - } - } else { - titleSegments.unshift(route.params.item) - } + const glue = ' - ' + const titleSegments = [routeTitle] + const generalName = this.configuration.currentTheme.general.name + return { + shortDocumentTitle: titleSegments.join(glue), + fullDocumentTitle: [...titleSegments, generalName].join(glue) } - - return titleSegments.join(' - ') } } }) diff --git a/packages/web-runtime/src/index.ts b/packages/web-runtime/src/index.ts index eb95efa57dc..51b7941326c 100644 --- a/packages/web-runtime/src/index.ts +++ b/packages/web-runtime/src/index.ts @@ -30,6 +30,7 @@ import { import { buildPublicSpaceResource, buildSpace, + isPersonalSpaceResource, isPublicSpaceResource, Resource } from 'web-client/src/helpers' @@ -102,7 +103,16 @@ export const renderSuccess = (): void => { store.getters.configuration.server, store.getters['runtime/auth/accessToken'] ) - return store.dispatch('runtime/spaces/loadSpaces', { graphClient }) + await store.dispatch('runtime/spaces/loadSpaces', { graphClient }) + const personalSpace = store.getters['runtime/spaces/spaces'].find((space) => + isPersonalSpaceResource(space) + ) + store.commit('runtime/spaces/UPDATE_SPACE_FIELD', { + id: personalSpace.id, + field: 'name', + value: instance.$gettext('Personal') + }) + return } // Spaces feature not available. Create a virtual personal space @@ -111,7 +121,7 @@ export const renderSuccess = (): void => { id: user.id, driveAlias: `personal/${user.id}`, driveType: 'personal', - name: user.id, + name: instance.$gettext('All files'), webDavPath: `/files/${user.id}`, serverUrl: configurationManager.serverUrl }) @@ -143,6 +153,7 @@ export const renderSuccess = (): void => { const publicLinkPassword = store.getters['runtime/auth/publicLinkPassword'] const space = buildPublicSpaceResource({ id: publicLinkToken, + name: instance.$gettext('Public files'), ...(publicLinkPassword && { publicLinkPassword }), serverUrl: configurationManager.serverUrl })