Add a breadcrumbs component

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2022-01-10 15:19:16 +01:00
parent 40758a83d5
commit 1daa8c5f5a
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
35 changed files with 695 additions and 830 deletions

View file

@ -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>

View file

@ -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([

View file

@ -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>

View file

@ -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="{
<breadcrumbs-nav
:links="[
{ name: RouteName.ADMIN, text: $t('Admin') },
{
name: RouteName.ADMIN_GROUPS,
}"
>{{ $t("Groups") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Groups'),
},
{
name: RouteName.PROFILES,
params: { id: group.id },
}"
>{{ group.name || usernameWithDomain(group) }}</router-link
>
</li>
</ul>
</nav>
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;

View file

@ -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="{
<breadcrumbs-nav
:links="[
{ name: RouteName.ADMIN, text: $t('Admin') },
{
name: RouteName.PROFILES,
}"
>{{ $t("Profiles") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Profiles'),
},
{
name: RouteName.PROFILES,
params: { id: person.id },
}"
>{{ person.name || person.preferredUsername }}</router-link
>
</li>
</ul>
</nav>
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;

View file

@ -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="{
<breadcrumbs-nav
:links="[
{ name: RouteName.ADMIN, text: $t('Admin') },
{
name: RouteName.USERS,
}"
>{{ $t("Users") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Users'),
},
{
name: RouteName.ADMIN_USER_PROFILE,
params: { id: user.id },
}"
>{{ user.email }}</router-link
>
</li>
</ul>
</nav>
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">

View file

@ -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">

View file

@ -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"

View file

@ -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">

View file

@ -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"

View file

@ -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>

View file

@ -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">

View file

@ -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"

View file

@ -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="{
<breadcrumbs-nav
v-if="group"
:links="[
{
name: RouteName.MY_GROUPS,
text: $t('My groups'),
},
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li>
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.DISCUSSION_LIST,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Discussions") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Discussions'),
},
{
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>
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 {

View file

@ -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="{
<breadcrumbs-nav
v-if="group"
:links="[
{
name: RouteName.MY_GROUPS,
text: $t('My groups'),
},
{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(discussion.actor),
params: { preferredUsername: usernameWithDomain(group) },
text: displayName(group),
},
}"
>{{ 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),
params: { preferredUsername: usernameWithDomain(group) },
text: $t('Discussions'),
},
}"
>{{ $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>
{
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> {

View file

@ -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="{
<breadcrumbs-nav
:links="[
{
name: RouteName.MY_GROUPS,
text: $t('My groups'),
},
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.DISCUSSION_LIST,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Discussions") }}</router-link
>
</li>
</ul>
</nav>
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;

View file

@ -1,27 +1,19 @@
<template>
<div class="container section" v-if="group">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link
:to="{
<breadcrumbs-nav
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
name: RouteName.TODO_LISTS,
text: displayName(group),
},
{
name: RouteName.EVENTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Events") }}</router-link
>
</li>
</ul>
</nav>
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;

View file

@ -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="{
<breadcrumbs-nav
:links="[
{ name: RouteName.MY_EVENTS, text: $t('My events') },
{
name: RouteName.EVENT,
params: { uuid: event.uuid },
}"
>{{ event.title }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: event.title,
},
{
name: RouteName.PARTICIPANTS,
params: { uuid: event.uuid },
}"
>{{ $t("Participants") }}</router-link
>
</li>
</ul>
</nav>
text: $t('Participants'),
},
]"
/>
<h1 class="title">{{ $t("Participants") }}</h1>
<div class="level">
<div class="level-left">

View file

@ -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="{
<breadcrumbs-nav
v-if="group"
:links="[
{ name: RouteName.MY_GROUPS, text: $t('My groups') },
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
</ul>
</nav>
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;

View file

@ -1,36 +1,25 @@
<template>
<div>
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul v-if="group">
<li>
<router-link
:to="{
<breadcrumbs-nav
v-if="group"
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li>
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.GROUP_SETTINGS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Settings") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Settings'),
},
{
name: RouteName.GROUP_FOLLOWERS_SETTINGS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Followers") }}</router-link
>
</li>
</ul>
</nav>
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 {

View file

@ -1,36 +1,25 @@
<template>
<div>
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul v-if="group">
<li>
<router-link
:to="{
<breadcrumbs-nav
v-if="group"
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li>
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.GROUP_SETTINGS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Settings") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Settings'),
},
{
name: RouteName.GROUP_MEMBERS_SETTINGS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Members") }}</router-link
>
</li>
</ul>
</nav>
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)) {

View file

@ -1,37 +1,25 @@
<template>
<div>
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link
<breadcrumbs-nav
v-if="group"
:to="{
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name || usernameWithDomain(group) }}</router-link
>
</li>
<li>
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.GROUP_SETTINGS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Settings") }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: $t('Settings'),
},
{
name: RouteName.GROUP_PUBLIC_SETTINGS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Group settings") }}</router-link
>
</li>
</ul>
</nav>
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;

View file

@ -1,27 +1,21 @@
<template>
<div class="container section">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul v-if="group">
<li>
<router-link
:to="{
<breadcrumbs-nav
v-if="group"
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li>
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.TIMELINE,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Activity") }}</router-link
>
</li>
</ul>
</nav>
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;

View file

@ -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">

View file

@ -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"

View file

@ -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";

View file

@ -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>

View file

@ -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="{
<breadcrumbs-nav
:links="[
{
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="{
text: displayName(group),
},
{
name: RouteName.POSTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Posts") }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
</ul>
</nav>
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 {

View file

@ -1,47 +1,6 @@
<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>
<breadcrumbs-nav :links="breadcrumbLinks">
<li>
<b-dropdown aria-role="list">
<b-button class="button is-primary" slot="trigger">+</b-button>
@ -73,8 +32,7 @@
</b-dropdown-item>
</b-dropdown>
</li>
</ul>
</nav>
</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>

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -1,35 +1,29 @@
<template>
<section class="section container" v-if="todo">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link
:to="{
<breadcrumbs-nav
:links="[
{
name: RouteName.GROUP,
params: {
preferredUsername: todo.todoList.actor.preferredUsername,
preferredUsername: usernameWithDomain(todo.todoList.actor),
},
}"
>{{ todo.todoList.actor.name }}</router-link
>
</li>
<li>
<router-link
:to="{
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 },
}"
>
{{ 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>
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>

View file

@ -1,34 +1,24 @@
<template>
<section class="container section" v-if="todoList">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link
:to="{
<breadcrumbs-nav
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: todoList.actor.preferredUsername },
}"
>{{ todoList.actor.name }}</router-link
>
</li>
<li>
<router-link
:to="{
params: { preferredUsername: usernameWithDomain(todoList.actor) },
text: displayName(group),
},
{
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>
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,

View file

@ -1,27 +1,19 @@
<template>
<div class="container section" v-if="group">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link
:to="{
<breadcrumbs-nav
:links="[
{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
text: displayName(group),
},
{
name: RouteName.TODO_LISTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Task lists") }}</router-link
>
</li>
</ul>
</nav>
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;
}