feat(settings): Allow app discover links to open app routes or the appstore page

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/44259/head
Ferdinand Thiessen 3 months ago
parent 44e0223ef9
commit 7b1d1b329e
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400

@ -0,0 +1,117 @@
<!--
- @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/>.
-
-->
<template>
<a v-if="linkProps" v-bind="linkProps">
<slot />
</a>
<RouterLink v-else-if="routerProps" v-bind="routerProps">
<slot />
</RouterLink>
</template>
<script lang="ts">
import type { RouterLinkProps } from 'vue-router/types/router.js'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
import { defineComponent } from 'vue'
import { RouterLink } from 'vue-router'
const knownRoutes = Object.fromEntries(
Object.entries(
loadState<Record<string, { app?: string, href: string }>>('core', 'apps'),
).map(([k, v]) => [v.app ?? k, v.href]),
)
/**
* This component either shows a native link to the installed app or external size - or a router link to the appstore page of the app if not installed
*/
export default defineComponent({
name: 'AppLink',
components: { RouterLink },
props: {
href: {
type: String,
required: true,
},
},
data() {
return {
routerProps: undefined as RouterLinkProps|undefined,
linkProps: undefined as Record<string, string>|undefined,
}
},
watch: {
href: {
immediate: true,
handler() {
const match = this.href.match(/^app:\/\/([^/]+)(\/.+)?$/)
this.routerProps = undefined
this.linkProps = undefined
// not an app url
if (match === null) {
this.linkProps = {
href: this.href,
target: '_blank',
rel: 'noreferrer noopener',
}
return
}
const appId = match[1]
// Check if specific route was requested
if (match[2]) {
// we do no know anything about app internal path so we only allow generic app paths
this.linkProps = {
href: generateUrl(`/apps/${appId}${match[2]}`),
}
return
}
// If we know any route for that app we open it
if (appId in knownRoutes) {
this.linkProps = {
href: knownRoutes[appId],
}
return
}
// Fallback to show the app store entry
this.routerProps = {
to: {
name: 'apps-details',
params: {
category: 'installed',
id: appId,
},
},
}
},
},
},
})
</script>

@ -23,18 +23,18 @@
<article :id="domId"
class="app-discover-post"
:class="{ 'app-discover-post--reverse': media && media.alignment === 'start' }">
<component :is="link ? 'a' : 'div'"
<component :is="link ? 'AppLink' : 'div'"
v-if="headline || text"
:href="link"
:target="link ? '_blank' : undefined"
class="app-discover-post__text">
<component :is="inline ? 'h4' : 'h3'">{{ translatedHeadline }}</component>
<component :is="inline ? 'h4' : 'h3'">
{{ translatedHeadline }}
</component>
<p>{{ translatedText }}</p>
</component>
<component :is="mediaLink ? 'a' : 'div'"
<component :is="mediaLink ? 'AppLink' : 'div'"
v-if="mediaSources"
:href="mediaLink"
:target="mediaLink ? '_blank' : undefined"
class="app-discover-post__media"
:class="{
'app-discover-post__media--fullwidth': isFullWidth,
@ -79,9 +79,11 @@ import { commonAppDiscoverProps } from './common'
import { useLocalizedValue } from '../../composables/useGetLocalizedValue'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import AppLink from './AppLink.vue'
export default defineComponent({
components: {
AppLink,
NcIconSvgWrapper,
},
@ -116,8 +118,8 @@ export default defineComponent({
setup(props) {
const translatedHeadline = useLocalizedValue(computed(() => props.headline))
const translatedText = useLocalizedValue(computed(() => props.text))
const localizedMedia = useLocalizedValue(computed(() => props.media?.content))
const mediaSources = computed(() => localizedMedia.value !== null ? [localizedMedia.value.src].flat() : undefined)
const mediaAlt = computed(() => localizedMedia.value?.alt ?? '')

Loading…
Cancel
Save