diff --git a/js/src/components/Utils/Breadcrumbs.vue b/js/src/components/Utils/Breadcrumbs.vue new file mode 100644 index 000000000..a9f775a25 --- /dev/null +++ b/js/src/components/Utils/Breadcrumbs.vue @@ -0,0 +1,69 @@ +<template> + <nav class="flex mb-3" :aria-label="$t('Breadcrumbs')"> + <ol class="inline-flex items-center space-x-1 md:space-x-3"> + <li + class="inline-flex items-center" + v-for="(element, index) in links" + :key="index" + :aria-current="index > 0 ? 'page' : undefined" + > + <router-link + v-if="index === 0" + :to="element" + class="inline-flex items-center text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white" + > + {{ element.text }} + </router-link> + <div class="flex items-center" v-else-if="index === links.length - 1"> + <svg + class="w-6 h-6 text-gray-400 rtl:rotate-180" + fill="currentColor" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + > + <path + fill-rule="evenodd" + d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" + clip-rule="evenodd" + ></path> + </svg> + <span + class="ltr:ml-1 rtl:mr-1 font-medium text-gray-400 md:ltr:ml-2 md:rtl:mr-2 dark:text-gray-500" + >{{ element.text }}</span + > + </div> + <div class="flex items-center" v-else> + <svg + class="w-6 h-6 text-gray-400 rtl:rotate-180" + fill="currentColor" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + > + <path + fill-rule="evenodd" + d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" + clip-rule="evenodd" + ></path> + </svg> + <router-link + :to="element" + class="ltr:ml-1 rtl:mr-1 font-medium text-gray-700 hover:text-gray-900 md:ltr:ml-2 md:rtl:mr-2 dark:text-gray-400 dark:hover:text-white" + >{{ element.text }}</router-link + > + </div> + </li> + <slot></slot> + </ol> + </nav> +</template> +<script lang="ts"> +import { Component, Prop, Vue } from "vue-property-decorator"; +import { Location } from "vue-router"; + +type LinkElement = Location & { text: string }; + +@Component +export default class Breadcrumbs extends Vue { + @Prop({ type: Array, required: true }) links!: LinkElement[]; +} +</script> diff --git a/js/src/main.ts b/js/src/main.ts index 317f2b37b..0e6695195 100644 --- a/js/src/main.ts +++ b/js/src/main.ts @@ -12,6 +12,7 @@ import { NotifierPlugin } from "./plugins/notifier"; import filters from "./filters"; import { i18n } from "./utils/i18n"; import apolloProvider from "./vue-apollo"; +import Breadcrumbs from "@/components/Utils/Breadcrumbs.vue"; import "./registerServiceWorker"; import "./assets/tailwind.css"; @@ -25,6 +26,7 @@ Vue.use(VueScrollTo); Vue.use(VTooltip); Vue.use(VueAnnouncer); Vue.use(VueSkipTo); +Vue.component("breadcrumbs-nav", Breadcrumbs); // Register the router hooks with their names Component.registerHooks([ diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue index 0bba7acde..d0e56dd43 100644 --- a/js/src/views/Account/children/EditIdentity.vue +++ b/js/src/views/Account/children/EditIdentity.vue @@ -1,28 +1,6 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.IDENTITIES }">{{ - $t("Profiles") - }}</router-link> - </li> - <li class="is-active" v-if="isUpdate && identity"> - <router-link - :to="{ - name: RouteName.UPDATE_IDENTITY, - params: { identityName: identity.preferredUsername }, - }" - >{{ identity.name }}</router-link - > - </li> - <li class="is-active" v-else> - <router-link :to="{ name: RouteName.CREATE_IDENTITY }">{{ - $t("New profile") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav :links="breadcrumbsLinks" /> <div class="root" v-if="identity"> <h1 class="title"> <span v-if="isUpdate">{{ identity.displayName() }}</span> @@ -253,6 +231,7 @@ import { ServerParseError } from "@apollo/client/link/http"; import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; import pick from "lodash/pick"; import { ActorType } from "@/types/enums"; +import { Location } from "vue-router"; @Component({ components: { @@ -670,5 +649,29 @@ export default class EditIdentity extends mixins(identityEditionMixin) { this.oldDisplayName = null; this.avatarFile = null; } + + get breadcrumbsLinks(): (Location & { text: string })[] { + const links = [ + { + name: RouteName.IDENTITIES, + params: {}, + text: this.$t("Profiles") as string, + }, + ]; + if (this.isUpdate && this.identity) { + links.push({ + name: RouteName.UPDATE_IDENTITY, + params: { identityName: this.identity.preferredUsername }, + text: this.identity.name, + }); + } else { + links.push({ + name: RouteName.CREATE_IDENTITY, + params: {}, + text: this.$t("New profile") as string, + }); + } + return links; + } } </script> diff --git a/js/src/views/Admin/AdminGroupProfile.vue b/js/src/views/Admin/AdminGroupProfile.vue index 43e85e879..a330a69f8 100644 --- a/js/src/views/Admin/AdminGroupProfile.vue +++ b/js/src/views/Admin/AdminGroupProfile.vue @@ -1,31 +1,19 @@ <template> <div v-if="group" class="section"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li> - <router-link - :to="{ - name: RouteName.ADMIN_GROUPS, - }" - >{{ $t("Groups") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.PROFILES, - params: { id: group.id }, - }" - >{{ group.name || usernameWithDomain(group) }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { + name: RouteName.ADMIN_GROUPS, + text: $t('Groups'), + }, + { + name: RouteName.PROFILES, + params: { id: group.id }, + text: displayName(group), + }, + ]" + /> <div class="actor-card"> <p v-if="group.suspended"> <actor-card @@ -305,7 +293,11 @@ import { formatBytes } from "@/utils/datetime"; import { MemberRole } from "@/types/enums"; import { SUSPEND_PROFILE, UNSUSPEND_PROFILE } from "../../graphql/actor"; import { IGroup } from "../../types/actor"; -import { usernameWithDomain, IActor } from "../../types/actor/actor.model"; +import { + usernameWithDomain, + displayName, + IActor, +} from "../../types/actor/actor.model"; import RouteName from "../../router/name"; import ActorCard from "../../components/Account/ActorCard.vue"; import EmptyContent from "../../components/Utils/EmptyContent.vue"; @@ -359,6 +351,8 @@ export default class AdminGroupProfile extends Vue { usernameWithDomain = usernameWithDomain; + displayName = displayName; + RouteName = RouteName; EVENTS_PER_PAGE = EVENTS_PER_PAGE; diff --git a/js/src/views/Admin/AdminProfile.vue b/js/src/views/Admin/AdminProfile.vue index 32bb836e6..8677847ea 100644 --- a/js/src/views/Admin/AdminProfile.vue +++ b/js/src/views/Admin/AdminProfile.vue @@ -1,31 +1,20 @@ <template> <div v-if="person" class="section"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li> - <router-link - :to="{ - name: RouteName.PROFILES, - }" - >{{ $t("Profiles") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.PROFILES, - params: { id: person.id }, - }" - >{{ person.name || person.preferredUsername }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { + name: RouteName.PROFILES, + text: $t('Profiles'), + }, + { + name: RouteName.PROFILES, + params: { id: person.id }, + text: displayName(person), + }, + ]" + /> + <div class="actor-card"> <actor-card :actor="person" @@ -279,7 +268,7 @@ import { UNSUSPEND_PROFILE, } from "../../graphql/actor"; import { IPerson } from "../../types/actor"; -import { usernameWithDomain } from "../../types/actor/actor.model"; +import { displayName, usernameWithDomain } from "../../types/actor/actor.model"; import RouteName from "../../router/name"; import ActorCard from "../../components/Account/ActorCard.vue"; import EmptyContent from "../../components/Utils/EmptyContent.vue"; @@ -334,6 +323,8 @@ export default class AdminProfile extends Vue { usernameWithDomain = usernameWithDomain; + displayName = displayName; + RouteName = RouteName; EVENTS_PER_PAGE = EVENTS_PER_PAGE; diff --git a/js/src/views/Admin/AdminUserProfile.vue b/js/src/views/Admin/AdminUserProfile.vue index c4fcf3e9f..6905783f3 100644 --- a/js/src/views/Admin/AdminUserProfile.vue +++ b/js/src/views/Admin/AdminUserProfile.vue @@ -1,31 +1,20 @@ <template> <div v-if="user" class="section"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li> - <router-link - :to="{ - name: RouteName.USERS, - }" - >{{ $t("Users") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.ADMIN_USER_PROFILE, - params: { id: user.id }, - }" - >{{ user.email }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { + name: RouteName.USERS, + text: $t('Users'), + }, + { + name: RouteName.ADMIN_USER_PROFILE, + params: { id: user.id }, + text: user.email, + }, + ]" + /> + <table v-if="metadata.length > 0" class="table is-fullwidth"> <tbody> <tr v-for="{ key, value, link, elements, type } in metadata" :key="key"> diff --git a/js/src/views/Admin/Dashboard.vue b/js/src/views/Admin/Dashboard.vue index d2894f0c1..75adc8e9f 100644 --- a/js/src/views/Admin/Dashboard.vue +++ b/js/src/views/Admin/Dashboard.vue @@ -1,19 +1,11 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.ADMIN_DASHBOARD }">{{ - $t("Dashboard") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { text: $t('Dashboard') }, + ]" + /> <section> <h1 class="title">{{ $t("Administration") }}</h1> <div class="tile is-ancestor" v-if="dashboard"> diff --git a/js/src/views/Admin/GroupProfiles.vue b/js/src/views/Admin/GroupProfiles.vue index f6a8011c2..6b4c95173 100644 --- a/js/src/views/Admin/GroupProfiles.vue +++ b/js/src/views/Admin/GroupProfiles.vue @@ -1,19 +1,14 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MODERATION }">{{ - $t("Moderation") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.PROFILES }">{{ - $t("Groups") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.MODERATION, text: $t('Moderation') }, + { + name: RouteName.ADMIN_GROUPS, + text: $t('Groups'), + }, + ]" + /> <div class="buttons" v-if="showCreateGroupsButton"> <router-link class="button is-primary" diff --git a/js/src/views/Admin/Instance.vue b/js/src/views/Admin/Instance.vue index bfb4bd18c..399b8a558 100644 --- a/js/src/views/Admin/Instance.vue +++ b/js/src/views/Admin/Instance.vue @@ -1,28 +1,12 @@ <template> <div v-if="instance"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li> - <router-link :to="{ name: RouteName.INSTANCES }">{{ - $t("Instances") - }}</router-link> - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.INSTANCE, - params: { domain: instance.domain }, - }" - >{{ instance.domain }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { name: RouteName.INSTANCES, text: $t('Instances') }, + { text: instance.domain }, + ]" + /> <h1 class="text-2xl">{{ instance.domain }}</h1> <div class="grid md:grid-cols-4 gap-2 content-center text-center mt-2"> <div class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800"> diff --git a/js/src/views/Admin/Instances.vue b/js/src/views/Admin/Instances.vue index e2c64940f..5856f5ef3 100644 --- a/js/src/views/Admin/Instances.vue +++ b/js/src/views/Admin/Instances.vue @@ -1,28 +1,16 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.INSTANCES }">{{ - $t("Instances") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { text: $t('Instances') }, + ]" + /> <section> <h1 class="title">{{ $t("Instances") }}</h1> <form @submit="followInstance" class="my-4"> - <b-field - :label="$t('Follow a new instance')" - custom-class="add-relay" - horizontal - > - <b-field grouped expanded size="is-large"> + <b-field :label="$t('Follow a new instance')" horizontal> + <b-field grouped group-multiline expanded size="is-large"> <p class="control"> <b-input v-model="newRelayAddress" diff --git a/js/src/views/Admin/Profiles.vue b/js/src/views/Admin/Profiles.vue index 1a61483fb..cfd4f8d49 100644 --- a/js/src/views/Admin/Profiles.vue +++ b/js/src/views/Admin/Profiles.vue @@ -1,19 +1,14 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MODERATION }">{{ - $t("Moderation") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.PROFILES }">{{ - $t("Profiles") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.MODERATION, text: $t('Moderation') }, + { + name: RouteName.PROFILES, + text: $t('Profiles'), + }, + ]" + /> <div v-if="persons"> <b-switch v-model="local">{{ $t("Local") }}</b-switch> <b-switch v-model="suspended">{{ $t("Suspended") }}</b-switch> diff --git a/js/src/views/Admin/Settings.vue b/js/src/views/Admin/Settings.vue index 9a8e8c90c..137e7907a 100644 --- a/js/src/views/Admin/Settings.vue +++ b/js/src/views/Admin/Settings.vue @@ -1,19 +1,12 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ADMIN }">{{ - $t("Admin") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.ADMIN_SETTINGS }">{{ - $t("Instance settings") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.ADMIN, text: $t('Admin') }, + { text: $t('Instance settings') }, + ]" + /> + <section v-if="settingsToWrite"> <form @submit.prevent="updateSettings"> <b-field :label="$t('Instance Name')" label-for="instance-name"> diff --git a/js/src/views/Admin/Users.vue b/js/src/views/Admin/Users.vue index 4f7a7ca3b..c018f3366 100644 --- a/js/src/views/Admin/Users.vue +++ b/js/src/views/Admin/Users.vue @@ -1,19 +1,14 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MODERATION }">{{ - $t("Moderation") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.USERS }">{{ - $t("Users") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.MODERATION, text: $t('Moderation') }, + { + name: RouteName.USERS, + text: $t('Users'), + }, + ]" + /> <div v-if="users"> <b-table :data="users.elements" diff --git a/js/src/views/Discussions/Create.vue b/js/src/views/Discussions/Create.vue index bbcbf560a..2659ba27d 100644 --- a/js/src/views/Discussions/Create.vue +++ b/js/src/views/Discussions/Create.vue @@ -1,42 +1,29 @@ <template> <section class="section container"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul v-if="group"> - <li> - <router-link :to="{ name: RouteName.MY_GROUPS }">{{ - $t("My groups") - }}</router-link> - </li> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.DISCUSSION_LIST, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Discussions") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.CREATE_DISCUSSION, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Create") }}</router-link - > - </li> - </ul> - <b-skeleton v-else-if="$apollo.loading" :animated="animated"></b-skeleton> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { + name: RouteName.MY_GROUPS, + text: $t('My groups'), + }, + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.DISCUSSION_LIST, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Discussions'), + }, + { + name: RouteName.CREATE_DISCUSSION, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Create'), + }, + ]" + /> <h1 class="title">{{ $t("Create a discussion") }}</h1> <form @submit.prevent="createDiscussion"> @@ -67,7 +54,12 @@ <script lang="ts"> import { Component, Prop, Vue } from "vue-property-decorator"; -import { IGroup, IPerson, usernameWithDomain } from "@/types/actor"; +import { + displayName, + IGroup, + IPerson, + usernameWithDomain, +} from "@/types/actor"; import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; import { FETCH_GROUP } from "@/graphql/group"; import { CREATE_DISCUSSION } from "@/graphql/discussion"; @@ -113,6 +105,8 @@ export default class CreateDiscussion extends Vue { usernameWithDomain = usernameWithDomain; + displayName = displayName; + async createDiscussion(): Promise<void> { this.errors = { title: "" }; try { diff --git a/js/src/views/Discussions/Discussion.vue b/js/src/views/Discussions/Discussion.vue index b61457dc7..b693b903c 100644 --- a/js/src/views/Discussions/Discussion.vue +++ b/js/src/views/Discussions/Discussion.vue @@ -1,46 +1,29 @@ <template> <div class="container section" v-if="discussion"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MY_GROUPS }">{{ - $t("My groups") - }}</router-link> - </li> - <li> - <router-link - v-if="discussion.actor" - :to="{ - name: RouteName.GROUP, - params: { - preferredUsername: usernameWithDomain(discussion.actor), - }, - }" - >{{ discussion.actor.name }}</router-link - > - <b-skeleton v-else-if="$apollo.loading" animated /> - </li> - <li> - <router-link - v-if="discussion.actor" - :to="{ - name: RouteName.DISCUSSION_LIST, - params: { - preferredUsername: usernameWithDomain(discussion.actor), - }, - }" - >{{ $t("Discussions") }}</router-link - > - <b-skeleton animated v-else-if="$apollo.loading" /> - </li> - <li class="is-active"> - <router-link - :to="{ name: RouteName.DISCUSSION, params: { id: discussion.id } }" - >{{ discussion.title }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { + name: RouteName.MY_GROUPS, + text: $t('My groups'), + }, + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.DISCUSSION_LIST, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Discussions'), + }, + { + name: RouteName.DISCUSSION, + params: { id: discussion.id }, + text: discussion.title, + }, + ]" + /> <b-message v-if="error" type="is-danger"> {{ error }} </b-message> @@ -148,7 +131,7 @@ import { } from "@/graphql/discussion"; import { IDiscussion } from "@/types/discussions"; import { Discussion as DiscussionModel } from "@/types/discussions"; -import { usernameWithDomain } from "@/types/actor"; +import { displayName, usernameWithDomain } from "@/types/actor"; import DiscussionComment from "@/components/Discussion/DiscussionComment.vue"; import { GraphQLError } from "graphql"; import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment"; @@ -250,6 +233,7 @@ export default class Discussion extends mixins(GroupMixin) { RouteName = RouteName; usernameWithDomain = usernameWithDomain; + displayName = displayName; error: string | null = null; async reply(): Promise<void> { diff --git a/js/src/views/Discussions/DiscussionsList.vue b/js/src/views/Discussions/DiscussionsList.vue index 63e016138..c9e63677d 100644 --- a/js/src/views/Discussions/DiscussionsList.vue +++ b/js/src/views/Discussions/DiscussionsList.vue @@ -1,32 +1,23 @@ <template> <div class="container section" v-if="group"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MY_GROUPS }">{{ - $t("My groups") - }}</router-link> - </li> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.DISCUSSION_LIST, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Discussions") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.MY_GROUPS, + text: $t('My groups'), + }, + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.DISCUSSION_LIST, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Discussions'), + }, + ]" + /> <section v-if="isCurrentActorAGroupMember"> <p> {{ @@ -82,7 +73,13 @@ <script lang="ts"> import { Component, Prop, Vue } from "vue-property-decorator"; import { FETCH_GROUP } from "@/graphql/group"; -import { IActor, IGroup, IPerson, usernameWithDomain } from "@/types/actor"; +import { + displayName, + IActor, + IGroup, + IPerson, + usernameWithDomain, +} from "@/types/actor"; import DiscussionListItem from "@/components/Discussion/DiscussionListItem.vue"; import RouteName from "../../router/name"; import { MemberRole } from "@/types/enums"; @@ -166,6 +163,7 @@ export default class DiscussionsList extends Vue { RouteName = RouteName; usernameWithDomain = usernameWithDomain; + displayName = displayName; DISCUSSIONS_PER_PAGE = DISCUSSIONS_PER_PAGE; diff --git a/js/src/views/Event/GroupEvents.vue b/js/src/views/Event/GroupEvents.vue index 5c3567bed..642892a9b 100644 --- a/js/src/views/Event/GroupEvents.vue +++ b/js/src/views/Event/GroupEvents.vue @@ -1,27 +1,19 @@ <template> <div class="container section" v-if="group"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.TODO_LISTS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Events") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.EVENTS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Events'), + }, + ]" + /> <section> <h1 class="title" v-if="group"> {{ @@ -89,7 +81,7 @@ import { PERSON_MEMBERSHIPS } from "@/graphql/actor"; import GroupMixin from "@/mixins/group"; import { IMember } from "@/types/actor/member.model"; import { FETCH_GROUP_EVENTS } from "@/graphql/event"; -import { usernameWithDomain } from "../../types/actor"; +import { displayName, usernameWithDomain } from "../../types/actor"; const EVENTS_PAGE_LIMIT = 10; @@ -143,6 +135,8 @@ export default class GroupEvents extends mixins(GroupMixin) { usernameWithDomain = usernameWithDomain; + displayName = displayName; + RouteName = RouteName; EVENTS_PAGE_LIMIT = EVENTS_PAGE_LIMIT; diff --git a/js/src/views/Event/Participants.vue b/js/src/views/Event/Participants.vue index 247f7fa71..b4ab755f6 100644 --- a/js/src/views/Event/Participants.vue +++ b/js/src/views/Event/Participants.vue @@ -1,32 +1,20 @@ <template> <section class="section container" v-if="event"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MY_EVENTS }">{{ - $t("My events") - }}</router-link> - </li> - <li> - <router-link - :to="{ - name: RouteName.EVENT, - params: { uuid: event.uuid }, - }" - >{{ event.title }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.PARTICIPANTS, - params: { uuid: event.uuid }, - }" - >{{ $t("Participants") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { name: RouteName.MY_EVENTS, text: $t('My events') }, + { + name: RouteName.EVENT, + params: { uuid: event.uuid }, + text: event.title, + }, + { + name: RouteName.PARTICIPANTS, + params: { uuid: event.uuid }, + text: $t('Participants'), + }, + ]" + /> <h1 class="title">{{ $t("Participants") }}</h1> <div class="level"> <div class="level-left"> diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue index ec29e8af1..2ed8013ac 100644 --- a/js/src/views/Group/Group.vue +++ b/js/src/views/Group/Group.vue @@ -1,27 +1,17 @@ <template> <div class="container is-widescreen"> <div class="header"> - <nav class="breadcrumb" :aria-label="$t('Breadcrumbs')"> - <ul> - <li> - <router-link :to="{ name: RouteName.MY_GROUPS }">{{ - $t("My groups") - }}</router-link> - </li> - <li class="is-active"> - <router-link - aria-current-value="location" - v-if="group && group.preferredUsername" - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - <b-skeleton v-else :animated="true"></b-skeleton> - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { name: RouteName.MY_GROUPS, text: $t('My groups') }, + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + ]" + /> <b-loading :active.sync="$apollo.loading"></b-loading> <header class="block-container presentation" v-if="group"> <div class="banner-container"> @@ -776,6 +766,8 @@ export default class Group extends mixins(GroupMixin) { usernameWithDomain = usernameWithDomain; + displayName = displayName; + PostVisibility = PostVisibility; Openness = Openness; diff --git a/js/src/views/Group/GroupFollowers.vue b/js/src/views/Group/GroupFollowers.vue index 8887b8a29..18c004cd3 100644 --- a/js/src/views/Group/GroupFollowers.vue +++ b/js/src/views/Group/GroupFollowers.vue @@ -1,36 +1,25 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul v-if="group"> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.GROUP_SETTINGS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Settings") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.GROUP_FOLLOWERS_SETTINGS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Followers") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.GROUP_SETTINGS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Settings'), + }, + { + name: RouteName.GROUP_FOLLOWERS_SETTINGS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Followers'), + }, + ]" + /> <b-loading :active="$apollo.loading" /> <section class="container section" @@ -138,7 +127,7 @@ import GroupMixin from "@/mixins/group"; import { mixins } from "vue-class-component"; import { GROUP_FOLLOWERS, UPDATE_FOLLOWER } from "@/graphql/followers"; import RouteName from "../../router/name"; -import { usernameWithDomain } from "../../types/actor"; +import { displayName, usernameWithDomain } from "../../types/actor"; import EmptyContent from "@/components/Utils/EmptyContent.vue"; import { IFollower } from "@/types/actor/follower.model"; import { Paginate } from "@/types/paginate"; @@ -181,6 +170,8 @@ export default class GroupFollowers extends mixins(GroupMixin) { usernameWithDomain = usernameWithDomain; + displayName = displayName; + followers!: Paginate<IFollower>; mounted(): void { diff --git a/js/src/views/Group/GroupMembers.vue b/js/src/views/Group/GroupMembers.vue index 3904299bf..a72185f26 100644 --- a/js/src/views/Group/GroupMembers.vue +++ b/js/src/views/Group/GroupMembers.vue @@ -1,36 +1,25 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul v-if="group"> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.GROUP_SETTINGS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Settings") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.GROUP_MEMBERS_SETTINGS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Members") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.GROUP_SETTINGS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Settings'), + }, + { + name: RouteName.GROUP_MEMBERS_SETTINGS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Members'), + }, + ]" + /> <b-loading :active="$apollo.loading" /> <section class="container section" @@ -312,6 +301,8 @@ export default class GroupMembers extends mixins(GroupMixin) { usernameWithDomain = usernameWithDomain; + displayName = displayName; + mounted(): void { const roleQuery = this.$route.query.role as string; if (Object.values(MemberRole).includes(roleQuery as MemberRole)) { diff --git a/js/src/views/Group/GroupSettings.vue b/js/src/views/Group/GroupSettings.vue index 894c38402..da70ab3ef 100644 --- a/js/src/views/Group/GroupSettings.vue +++ b/js/src/views/Group/GroupSettings.vue @@ -1,37 +1,25 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link - v-if="group" - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name || usernameWithDomain(group) }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.GROUP_SETTINGS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Settings") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.GROUP_PUBLIC_SETTINGS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Group settings") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.GROUP_SETTINGS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Settings'), + }, + { + name: RouteName.GROUP_PUBLIC_SETTINGS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Group settings'), + }, + ]" + /> <b-loading :active="$apollo.loading" /> <section class="container section" @@ -197,7 +185,12 @@ import { mixins } from "vue-class-component"; import GroupMixin from "@/mixins/group"; import { GroupVisibility, Openness } from "@/types/enums"; import { UPDATE_GROUP } from "../../graphql/group"; -import { Group, IGroup, usernameWithDomain } from "../../types/actor"; +import { + Group, + IGroup, + usernameWithDomain, + displayName, +} from "../../types/actor"; import { Address, IAddress } from "../../types/address.model"; import { CONFIG } from "@/graphql/config"; import { IConfig } from "@/types/config.model"; @@ -234,6 +227,8 @@ export default class GroupSettings extends mixins(GroupMixin) { usernameWithDomain = usernameWithDomain; + displayName = displayName; + GroupVisibility = GroupVisibility; Openness = Openness; diff --git a/js/src/views/Group/Timeline.vue b/js/src/views/Group/Timeline.vue index b66b3d111..e4b9944fb 100644 --- a/js/src/views/Group/Timeline.vue +++ b/js/src/views/Group/Timeline.vue @@ -1,27 +1,21 @@ <template> <div class="container section"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul v-if="group"> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.TIMELINE, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Activity") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="group" + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.TIMELINE, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Activity'), + }, + ]" + /> + <section class="timeline"> <b-field> <b-radio-button v-model="activityType" :native-value="undefined"> @@ -160,7 +154,7 @@ </template> <script lang="ts"> import { GROUP_TIMELINE } from "@/graphql/group"; -import { IGroup, usernameWithDomain } from "@/types/actor"; +import { IGroup, usernameWithDomain, displayName } from "@/types/actor"; import { ActivityType } from "@/types/enums"; import { Paginate } from "@/types/paginate"; import { Component, Prop, Vue } from "vue-property-decorator"; @@ -234,6 +228,8 @@ export default class Timeline extends Vue { usernameWithDomain = usernameWithDomain; + displayName = displayName; + ActivityType = ActivityType; ActivityAuthorFilter = ActivityAuthorFilter; diff --git a/js/src/views/Moderation/Logs.vue b/js/src/views/Moderation/Logs.vue index 4e6be0c7a..728781ce8 100644 --- a/js/src/views/Moderation/Logs.vue +++ b/js/src/views/Moderation/Logs.vue @@ -1,19 +1,17 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MODERATION }">{{ - $t("Moderation") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.REPORT_LOGS }">{{ - $t("Moderation log") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.MODERATION, + text: $t('Moderation'), + }, + { + name: RouteName.REPORT_LOGS, + text: $t('Moderation log'), + }, + ]" + /> <section v-if="actionLogs.total > 0 && actionLogs.elements.length > 0"> <ul> <li v-for="log in actionLogs.elements" :key="log.id"> diff --git a/js/src/views/Moderation/Report.vue b/js/src/views/Moderation/Report.vue index 59a94bf8b..71094d796 100644 --- a/js/src/views/Moderation/Report.vue +++ b/js/src/views/Moderation/Report.vue @@ -1,27 +1,23 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs" v-if="report"> - <ul> - <li> - <router-link :to="{ name: RouteName.MODERATION }">{{ - $t("Moderation") - }}</router-link> - </li> - <li> - <router-link :to="{ name: RouteName.REPORTS }">{{ - $t("Reports") - }}</router-link> - </li> - <li class="is-active"> - <router-link - :to="{ name: RouteName.REPORT, params: { id: report.id } }" - >{{ - $t("Report #{reportNumber}", { reportNumber: report.id }) - }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + v-if="report" + :links="[ + { + name: RouteName.MODERATION, + text: $t('Moderation'), + }, + { + name: RouteName.REPORTS, + text: $t('Reports'), + }, + { + name: RouteName.REPORT, + params: { id: report.id }, + text: $t('Report #{reportNumber}', { reportNumber: report.id }), + }, + ]" + /> <section> <b-message title="Error" diff --git a/js/src/views/Moderation/ReportList.vue b/js/src/views/Moderation/ReportList.vue index a1e278b9b..577842fdb 100644 --- a/js/src/views/Moderation/ReportList.vue +++ b/js/src/views/Moderation/ReportList.vue @@ -1,19 +1,17 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.MODERATION }">{{ - $t("Moderation") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.REPORTS }">{{ - $t("Reports") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.MODERATION, + text: $t('Moderation'), + }, + { + name: RouteName.REPORTS, + text: $t('Reports'), + }, + ]" + /> <section> <div class="flex flex-wrap gap-2"> <b-field :label="$t('Report status')"> @@ -93,7 +91,7 @@ </div> </template> <script lang="ts"> -import { Component, Ref, Vue } from "vue-property-decorator"; +import { Component, Vue } from "vue-property-decorator"; import { IReport } from "@/types/report.model"; import { REPORTS } from "@/graphql/report"; import ReportCard from "@/components/Report/ReportCard.vue"; diff --git a/js/src/views/Posts/Edit.vue b/js/src/views/Posts/Edit.vue index 8dcb2aadd..7719011fc 100644 --- a/js/src/views/Posts/Edit.vue +++ b/js/src/views/Posts/Edit.vue @@ -2,43 +2,7 @@ <div> <form @submit.prevent="publish(false)" v-if="isCurrentActorAGroupModerator"> <div class="container section"> - <nav class="breadcrumb" aria-label="breadcrumbs" v-if="actualGroup"> - <ul> - <li> - <router-link - v-if="actualGroup" - :to="{ - name: RouteName.GROUP, - params: { - preferredUsername: usernameWithDomain(actualGroup), - }, - }" - >{{ - actualGroup.name || actualGroup.preferredUsername - }}</router-link - > - <b-skeleton v-else :animated="true"></b-skeleton> - </li> - <li> - <router-link - v-if="actualGroup" - :to="{ - name: RouteName.POSTS, - params: { - preferredUsername: usernameWithDomain(actualGroup), - }, - }" - >{{ $t("Posts") }}</router-link - > - <b-skeleton v-else :animated="true"></b-skeleton> - </li> - <li class="is-active"> - <span v-if="preferredUsername">{{ $t("New post") }}</span> - <span v-else-if="slug">{{ $t("Edit post") }}</span> - <b-skeleton v-else :animated="true"></b-skeleton> - </li> - </ul> - </nav> + <breadcrumbs-nav v-if="actualGroup" :links="breadcrumbLinks" /> <h1 class="title" v-if="isUpdate === true"> {{ $t("Edit post") }} </h1> @@ -174,7 +138,7 @@ import { CREATE_POST, UPDATE_POST } from "../../graphql/post"; import { IPost } from "../../types/post.model"; import Editor from "../../components/Editor.vue"; -import { IActor, usernameWithDomain } from "../../types/actor"; +import { displayName, IActor, usernameWithDomain } from "../../types/actor"; import TagInput from "../../components/Event/TagInput.vue"; import RouteName from "../../router/name"; import Subtitle from "../../components/Utils/Subtitle.vue"; @@ -366,6 +330,39 @@ export default class EditPost extends mixins(GroupMixin, PostMixin) { } return this.group; } + + get breadcrumbLinks() { + const links = [ + { + name: RouteName.GROUP, + params: { + preferredUsername: usernameWithDomain(this.actualGroup), + }, + text: displayName(this.actualGroup), + }, + { + name: RouteName.POSTS, + params: { + preferredUsername: usernameWithDomain(this.actualGroup), + }, + text: this.$t("Posts"), + }, + ]; + if (this.preferredUsername) { + links.push({ + text: this.$t("New post") as string, + name: RouteName.POST_EDIT, + params: { preferredUsername: usernameWithDomain(this.actualGroup) }, + }); + } else { + links.push({ + text: this.$t("Edit post") as string, + name: RouteName.POST_EDIT, + params: { preferredUsername: usernameWithDomain(this.actualGroup) }, + }); + } + return links; + } } </script> <style lang="scss" scoped> diff --git a/js/src/views/Posts/List.vue b/js/src/views/Posts/List.vue index 502bf3a71..50bd858f7 100644 --- a/js/src/views/Posts/List.vue +++ b/js/src/views/Posts/List.vue @@ -1,31 +1,19 @@ <template> <div class="container section" v-if="group"> - <nav class="breadcrumb" aria-label="breadcrumbs" v-if="group"> - <ul> - <li> - <router-link - v-if="group" - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name || group.preferredUsername }}</router-link - > - <b-skeleton v-else :animated="true"></b-skeleton> - </li> - <li class="is-active"> - <router-link - v-if="group" - :to="{ - name: RouteName.POSTS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Posts") }}</router-link - > - <b-skeleton v-else :animated="true"></b-skeleton> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.POSTS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Posts'), + }, + ]" + /> <section> <div class="intro"> <p v-if="isCurrentActorMember"> @@ -84,7 +72,7 @@ import { IMember } from "@/types/actor/member.model"; import { FETCH_GROUP_POSTS } from "../../graphql/post"; import { Paginate } from "../../types/paginate"; import { IPost } from "../../types/post.model"; -import { usernameWithDomain } from "../../types/actor"; +import { usernameWithDomain, displayName } from "../../types/actor"; import RouteName from "../../router/name"; import MultiPostListItem from "../../components/Post/MultiPostListItem.vue"; @@ -148,6 +136,8 @@ export default class PostList extends mixins(GroupMixin) { usernameWithDomain = usernameWithDomain; + displayName = displayName; + POSTS_PAGE_LIMIT = POSTS_PAGE_LIMIT; get isCurrentActorMember(): boolean { diff --git a/js/src/views/Resources/ResourceFolder.vue b/js/src/views/Resources/ResourceFolder.vue index 539eaf0f0..6772ce9a8 100644 --- a/js/src/views/Resources/ResourceFolder.vue +++ b/js/src/views/Resources/ResourceFolder.vue @@ -1,80 +1,38 @@ <template> <div class="container section" v-if="resource"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(resource.actor) }, - }" - >{{ resource.actor.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.RESOURCE_FOLDER_ROOT, - params: { preferredUsername: usernameWithDomain(resource.actor) }, - }" - >{{ $t("Resources") }}</router-link - > - </li> - <li - :class="{ - 'is-active': - index + 1 === ResourceMixin.resourcePathArray(resource).length, - }" - v-for="(pathFragment, index) in filteredPath" - :key="pathFragment" - > - <router-link - :to="{ - name: RouteName.RESOURCE_FOLDER, - params: { - path: ResourceMixin.resourcePathArray(resource).slice( - 0, - index + 1 - ), - preferredUsername: usernameWithDomain(resource.actor), - }, - }" - >{{ pathFragment }}</router-link - > - </li> - <li> - <b-dropdown aria-role="list"> - <b-button class="button is-primary" slot="trigger">+</b-button> + <breadcrumbs-nav :links="breadcrumbLinks"> + <li> + <b-dropdown aria-role="list"> + <b-button class="button is-primary" slot="trigger">+</b-button> - <b-dropdown-item aria-role="listitem" @click="createFolderModal"> - <b-icon icon="folder" /> - {{ $t("New folder") }} - </b-dropdown-item> - <b-dropdown-item - aria-role="listitem" - @click="createLinkResourceModal = true" - > - <b-icon icon="link" /> - {{ $t("New link") }} - </b-dropdown-item> - <hr - role="presentation" - class="dropdown-divider" - v-if="resourceProviders.length" - /> - <b-dropdown-item - aria-role="listitem" - v-for="resourceProvider in resourceProviders" - :key="resourceProvider.software" - @click="createResourceFromProvider(resourceProvider)" - > - <b-icon :icon="mapServiceTypeToIcon[resourceProvider.software]" /> - {{ createSentenceForType(resourceProvider.software) }} - </b-dropdown-item> - </b-dropdown> - </li> - </ul> - </nav> + <b-dropdown-item aria-role="listitem" @click="createFolderModal"> + <b-icon icon="folder" /> + {{ $t("New folder") }} + </b-dropdown-item> + <b-dropdown-item + aria-role="listitem" + @click="createLinkResourceModal = true" + > + <b-icon icon="link" /> + {{ $t("New link") }} + </b-dropdown-item> + <hr + role="presentation" + class="dropdown-divider" + v-if="resourceProviders.length" + /> + <b-dropdown-item + aria-role="listitem" + v-for="resourceProvider in resourceProviders" + :key="resourceProvider.software" + @click="createResourceFromProvider(resourceProvider)" + > + <b-icon :icon="mapServiceTypeToIcon[resourceProvider.software]" /> + {{ createSentenceForType(resourceProvider.software) }} + </b-dropdown-item> + </b-dropdown> + </li> + </breadcrumbs-nav> <section> <p v-if="resource.path === '/'" class="module-description"> {{ @@ -276,7 +234,7 @@ import ResourceItem from "@/components/Resource/ResourceItem.vue"; import FolderItem from "@/components/Resource/FolderItem.vue"; import Draggable from "vuedraggable"; import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor"; -import { IActor, usernameWithDomain } from "../../types/actor"; +import { displayName, IActor, usernameWithDomain } from "../../types/actor"; import RouteName from "../../router/name"; import { IResource, @@ -764,6 +722,40 @@ export default class Resources extends Mixins(ResourceMixin) { } } } + + get breadcrumbLinks() { + if (!this.resource?.actor) return []; + const resourceActor = this.resource.actor; + const links = [ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(this.resource.actor) }, + text: displayName(this.resource.actor), + }, + { + name: RouteName.RESOURCE_FOLDER_ROOT, + params: { preferredUsername: usernameWithDomain(this.resource.actor) }, + text: this.$t("Resources") as string, + }, + ]; + + links.push( + ...this.filteredPath.map((pathFragment, index) => { + return { + name: RouteName.RESOURCE_FOLDER, + params: { + path: ResourceMixin.resourcePathArray(this.resource).slice( + 0, + index + 1 + ) as unknown as string, + preferredUsername: usernameWithDomain(resourceActor), + }, + text: pathFragment, + }; + }) + ); + return links; + } } </script> <style lang="scss" scoped> diff --git a/js/src/views/Settings/AccountSettings.vue b/js/src/views/Settings/AccountSettings.vue index 4cdb3a9ec..dbef8d39e 100644 --- a/js/src/views/Settings/AccountSettings.vue +++ b/js/src/views/Settings/AccountSettings.vue @@ -1,19 +1,17 @@ <template> <div v-if="loggedUser"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{ - $t("Account") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS_GENERAL }">{{ - $t("General") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.ACCOUNT_SETTINGS, + text: $t('Account'), + }, + { + name: RouteName.ACCOUNT_SETTINGS_GENERAL, + text: $t('General'), + }, + ]" + /> <section> <div class="setting-title"> <h2>{{ $t("Email") }}</h2> diff --git a/js/src/views/Settings/Notifications.vue b/js/src/views/Settings/Notifications.vue index efd052f90..2c463c97d 100644 --- a/js/src/views/Settings/Notifications.vue +++ b/js/src/views/Settings/Notifications.vue @@ -1,19 +1,17 @@ <template> <div v-if="loggedUser"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{ - $t("Account") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.NOTIFICATIONS }">{{ - $t("Notifications") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.ACCOUNT_SETTINGS, + text: $t('Account'), + }, + { + name: RouteName.NOTIFICATIONS, + text: $t('Notifications'), + }, + ]" + /> <section> <div class="setting-title"> <h2>{{ $t("Browser notifications") }}</h2> diff --git a/js/src/views/Settings/Preferences.vue b/js/src/views/Settings/Preferences.vue index 95543b864..d2980a9d5 100644 --- a/js/src/views/Settings/Preferences.vue +++ b/js/src/views/Settings/Preferences.vue @@ -1,19 +1,17 @@ <template> <div> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{ - $t("Account") - }}</router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.PREFERENCES }">{{ - $t("Preferences") - }}</router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.ACCOUNT_SETTINGS, + text: $t('Account'), + }, + { + name: RouteName.PREFERENCES, + text: $t('Preferences'), + }, + ]" + /> <div> <b-field :label="$t('Language')" label-for="setting-language"> <b-select diff --git a/js/src/views/Todos/Todo.vue b/js/src/views/Todos/Todo.vue index b8970e8a9..cdfe834f0 100644 --- a/js/src/views/Todos/Todo.vue +++ b/js/src/views/Todos/Todo.vue @@ -1,35 +1,29 @@ <template> <section class="section container" v-if="todo"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { - preferredUsername: todo.todoList.actor.preferredUsername, - }, - }" - >{{ todo.todoList.actor.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.TODO_LIST, - params: { id: todo.todoList.id }, - }" - > - {{ todo.todoList.title }} - </router-link> - </li> - <li class="is-active"> - <router-link :to="{ name: RouteName.TODO }" aria-current="page"> - {{ todo.title }} - </router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.GROUP, + params: { + preferredUsername: usernameWithDomain(todo.todoList.actor), + }, + text: displayName(todo.todoList.actor), + }, + { + name: RouteName.TODO_LISTS, + params: { + preferredUsername: usernameWithDomain(todo.todoList.actor), + }, + text: $t('Task lists'), + }, + { + name: RouteName.TODO_LIST, + params: { id: todo.todoList.id }, + text: todo.todoList.title, + }, + { name: RouteName.TODO, text: todo.title }, + ]" + /> <full-todo :todo="todo" /> </section> </template> @@ -39,6 +33,7 @@ import { GET_TODO } from "@/graphql/todos"; import { ITodo } from "@/types/todos"; import FullTodo from "@/components/Todo/FullTodo.vue"; import RouteName from "../../router/name"; +import { displayName, usernameWithDomain } from "@/types/actor"; @Component({ components: { @@ -70,5 +65,9 @@ export default class Todo extends Vue { todo!: ITodo; RouteName = RouteName; + + usernameWithDomain = usernameWithDomain; + + displayName = displayName; } </script> diff --git a/js/src/views/Todos/TodoList.vue b/js/src/views/Todos/TodoList.vue index 1aaa103ce..a48aab3c7 100644 --- a/js/src/views/Todos/TodoList.vue +++ b/js/src/views/Todos/TodoList.vue @@ -1,34 +1,24 @@ <template> <section class="container section" v-if="todoList"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: todoList.actor.preferredUsername }, - }" - >{{ todoList.actor.name }}</router-link - > - </li> - <li> - <router-link - :to="{ - name: RouteName.TODO_LISTS, - params: { preferredUsername: todoList.actor.preferredUsername }, - }" - >{{ $t("Task lists") }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ name: RouteName.TODO_LIST, params: { id: todoList.id } }" - > - {{ todoList.title }} - </router-link> - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(todoList.actor) }, + text: displayName(group), + }, + { + name: RouteName.TODO_LISTS, + params: { preferredUsername: usernameWithDomain(todoList.actor) }, + text: $t('Task lists'), + }, + { + name: RouteName.TODO_LIST, + params: { id: todoList.id }, + text: todoList.title, + }, + ]" + /> <h2 class="title">{{ todoList.title }}</h2> <div v-for="todo in todoList.todos.elements" :key="todo.id"> <compact-todo :todo="todo" /> @@ -48,7 +38,7 @@ import { ITodo } from "@/types/todos"; import { CREATE_TODO, FETCH_TODO_LIST } from "@/graphql/todos"; import CompactTodo from "@/components/Todo/CompactTodo.vue"; import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; -import { IActor } from "@/types/actor"; +import { displayName, IActor, usernameWithDomain } from "@/types/actor"; import { ITodoList } from "@/types/todolist"; import RouteName from "../../router/name"; import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core"; @@ -89,6 +79,10 @@ export default class TodoList extends Vue { RouteName = RouteName; + displayName = displayName; + + usernameWithDomain = usernameWithDomain; + async createNewTodo(): Promise<void> { await this.$apollo.mutate({ mutation: CREATE_TODO, diff --git a/js/src/views/Todos/TodoLists.vue b/js/src/views/Todos/TodoLists.vue index c58d7372f..14b26b926 100644 --- a/js/src/views/Todos/TodoLists.vue +++ b/js/src/views/Todos/TodoLists.vue @@ -1,27 +1,19 @@ <template> <div class="container section" v-if="group"> - <nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li> - <router-link - :to="{ - name: RouteName.GROUP, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ group.name }}</router-link - > - </li> - <li class="is-active"> - <router-link - :to="{ - name: RouteName.TODO_LISTS, - params: { preferredUsername: usernameWithDomain(group) }, - }" - >{{ $t("Task lists") }}</router-link - > - </li> - </ul> - </nav> + <breadcrumbs-nav + :links="[ + { + name: RouteName.GROUP, + params: { preferredUsername: usernameWithDomain(group) }, + text: displayName(group), + }, + { + name: RouteName.TODO_LISTS, + params: { preferredUsername: usernameWithDomain(group) }, + text: $t('Task lists'), + }, + ]" + /> <section> <p> {{ @@ -61,7 +53,7 @@ <script lang="ts"> import { Component, Prop, Vue } from "vue-property-decorator"; import { FETCH_GROUP } from "@/graphql/group"; -import { IGroup, usernameWithDomain } from "@/types/actor"; +import { IGroup, usernameWithDomain, displayName } from "@/types/actor"; import { CREATE_TODO_LIST } from "@/graphql/todos"; import CompactTodo from "@/components/Todo/CompactTodo.vue"; import { ITodoList } from "@/types/todolist"; @@ -108,6 +100,8 @@ export default class TodoLists extends Vue { usernameWithDomain = usernameWithDomain; + displayName = displayName; + get todoLists(): ITodoList[] { return this.group.todoLists.elements; }