@@ -9,6 +9,7 @@
|
||||
<o-notification
|
||||
v-if="isEventOrganiser && !areCommentsClosed"
|
||||
:closable="false"
|
||||
class="my-2"
|
||||
>{{ t("Comments are closed for everybody else.") }}</o-notification
|
||||
>
|
||||
<article class="flex flex-wrap items-start gap-2">
|
||||
|
||||
@@ -44,6 +44,7 @@ const comment = reactive<IComment>({
|
||||
|
||||
const deletedComment = reactive<IComment>({
|
||||
...comment,
|
||||
actor: null,
|
||||
deletedAt: new Date().toString(),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<article class="flex gap-1">
|
||||
<article class="flex gap-2">
|
||||
<div class="">
|
||||
<figure class="" v-if="comment.actor && comment.actor.avatar">
|
||||
<img
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
:date="event.beginsOn.toString()"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 w-full flex flex-col justify-between">
|
||||
<div class="w-full flex flex-col justify-between">
|
||||
<h3
|
||||
class="text-lg leading-5 line-clamp-3 font-bold text-violet-3"
|
||||
class="text-lg leading-5 line-clamp-3 font-bold text-violet-3 dark:text-white"
|
||||
:title="event.title"
|
||||
dir="auto"
|
||||
:lang="event.language"
|
||||
@@ -54,7 +54,10 @@
|
||||
{{ event.title }}
|
||||
</h3>
|
||||
<div class="pt-3">
|
||||
<div class="flex items-center text-violet-3" dir="auto">
|
||||
<div
|
||||
class="flex items-center text-violet-3 dark:text-white"
|
||||
dir="auto"
|
||||
>
|
||||
<figure class="" v-if="actorAvatarURL">
|
||||
<img
|
||||
class="rounded-xl"
|
||||
@@ -78,7 +81,7 @@
|
||||
dir="auto"
|
||||
v-else-if="event.options && event.options.isOnline"
|
||||
>
|
||||
<o-icon icon="video" />
|
||||
<Video />
|
||||
<span class="ltr:pl-2 rtl:pr-2">{{ $t("Online") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,6 +107,7 @@ import InlineAddress from "@/components/Address/InlineAddress.vue";
|
||||
import { computed } from "vue";
|
||||
import MobilizonTag from "../Tag.vue";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
import Video from "vue-material-design-icons/Video.vue";
|
||||
|
||||
const props = defineProps<{ event: IEvent; options?: IEventCardOptions }>();
|
||||
const defaultOptions: IEventCardOptions = {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<router-link
|
||||
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
|
||||
>
|
||||
<h2 class="text-2xl line-clamp-2">{{ event.title }}</h2>
|
||||
<h2 class="mt-0 line-clamp-2">{{ event.title }}</h2>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<div class="list-card flex flex-col relative">
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-1.5 gapt-x-3"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-1.5 gapt-x-3 pb-2"
|
||||
>
|
||||
<div class="mr-0 ml-0">
|
||||
<div class="h-36 relative w-full">
|
||||
@@ -89,6 +89,7 @@
|
||||
:physical-address="participation.event.physicalAddress"
|
||||
/>
|
||||
<div
|
||||
class="flex gap-1"
|
||||
v-else-if="
|
||||
participation.event.options &&
|
||||
participation.event.options.isOnline
|
||||
@@ -182,7 +183,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<o-dropdown aria-role="list">
|
||||
<o-dropdown aria-role="list" class="text-center self-center">
|
||||
<template #trigger>
|
||||
<o-button icon-right="dots-horizontal">
|
||||
{{ $t("Actions") }}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="multi-card-event">
|
||||
<div
|
||||
class="grid auto-rows-[1fr] gap-x-5 gap-y-8 grid-cols-[repeat(auto-fill,_minmax(250px,_1fr))]"
|
||||
>
|
||||
<event-card
|
||||
class="event-card"
|
||||
class="flex flex-col h-full"
|
||||
v-for="event in events"
|
||||
:event="event"
|
||||
:key="event.uuid"
|
||||
@@ -17,17 +19,3 @@ defineProps<{
|
||||
events: IEvent[];
|
||||
}>();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.multi-card-event {
|
||||
display: grid;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-column-gap: 20px;
|
||||
grid-row-gap: 30px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
.event-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
name: RouteName.GROUP,
|
||||
params: { preferredUsername: usernameWithDomain(group) },
|
||||
}"
|
||||
class="card flex flex-col max-w-md bg-white dark:bg-mbz-purple dark:text-white rounded shadow-lg"
|
||||
class="card flex flex-col max-w-md bg-white dark:bg-mbz-purple dark:text-white rounded-lg shadow-lg"
|
||||
>
|
||||
<figure class="rounded-t-lg flex justify-center h-1/4">
|
||||
<lazy-image-wrapper :picture="group.banner" :rounded="true" />
|
||||
|
||||
@@ -40,30 +40,30 @@
|
||||
},
|
||||
}"
|
||||
>
|
||||
<h2 class="text-2xl">{{ member.parent.name }}</h2>
|
||||
<div class="">
|
||||
<h2 class="mt-0">{{ member.parent.name }}</h2>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm">{{
|
||||
`@${usernameWithDomain(member.parent)}`
|
||||
}}</span>
|
||||
<div>
|
||||
<tag
|
||||
variant="info"
|
||||
v-if="member.role === MemberRole.ADMINISTRATOR"
|
||||
>{{ $t("Administrator") }}</tag
|
||||
>
|
||||
<tag
|
||||
variant="info"
|
||||
v-else-if="member.role === MemberRole.MODERATOR"
|
||||
>{{ $t("Moderator") }}</tag
|
||||
>
|
||||
</div>
|
||||
<tag
|
||||
variant="info"
|
||||
v-if="member.role === MemberRole.ADMINISTRATOR"
|
||||
>{{ $t("Administrator") }}</tag
|
||||
>
|
||||
<tag
|
||||
variant="info"
|
||||
v-else-if="member.role === MemberRole.MODERATOR"
|
||||
>{{ $t("Moderator") }}</tag
|
||||
>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 prose lg:prose-xl" v-if="member.parent.summary">
|
||||
<p v-html="member.parent.summary" />
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 prose dark:prose-invert lg:prose-xl line-clamp-2"
|
||||
v-if="member.parent.summary"
|
||||
v-html="flattenHTMLParagraphs(member.parent.summary)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<o-dropdown aria-role="list" position="bottom-left">
|
||||
@@ -95,6 +95,7 @@ import DotsHorizontal from "vue-material-design-icons/DotsHorizontal.vue";
|
||||
import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
import Tag from "@/components/Tag.vue";
|
||||
import { flattenHTMLParagraphs } from "@/utils/html";
|
||||
|
||||
defineProps<{
|
||||
member: IMember;
|
||||
|
||||
@@ -46,7 +46,7 @@ const notifier = inject<Notifier>("notifier");
|
||||
const onError = (error: ErrorResponse) => {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
notifier.error(error.graphQLErrors[0].message);
|
||||
notifier?.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,14 +8,13 @@
|
||||
:category="category"
|
||||
:with-details="false"
|
||||
/>
|
||||
<router-link :to="{ name: RouteName.CATEGORIES }">
|
||||
<div
|
||||
class="flex items-end brightness-85 h-36 w-36 md:h-52 md:w-52 rounded-lg font-semibold text-lg md:text-xl p-4 text-white bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500"
|
||||
>
|
||||
<span>
|
||||
{{ t("View all categories") }}
|
||||
</span>
|
||||
</div>
|
||||
<router-link
|
||||
:to="{ name: RouteName.CATEGORIES }"
|
||||
class="flex items-end brightness-85 h-36 w-36 md:h-52 md:w-52 rounded-lg font-semibold text-lg md:text-xl p-4 text-white bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500 hover:text-slate-200"
|
||||
>
|
||||
<span>
|
||||
{{ t("View all categories") }}
|
||||
</span>
|
||||
</router-link>
|
||||
</section>
|
||||
</template>
|
||||
@@ -63,6 +62,6 @@ const promotedCategories = computed((): CategoryStatsModel[] => {
|
||||
number,
|
||||
label: eventCategoryLabel(key),
|
||||
}))
|
||||
.slice(0, 10);
|
||||
.slice(0, 9);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { IAddress } from "@/types/address.model";
|
||||
import { AddressSearchType } from "@/types/enums";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import FullAddressAutoComplete from "../Event/FullAddressAutoComplete.vue";
|
||||
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
location: IAddress;
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
<template>
|
||||
<Story :setup-app="setupApp">
|
||||
<Story>
|
||||
<Variant :title="'Open'">
|
||||
<UnloggedIntroduction />
|
||||
<UnloggedIntroduction :config="config" />
|
||||
</Variant>
|
||||
<Variant :title="'Closed'">
|
||||
<UnloggedIntroduction />
|
||||
<UnloggedIntroduction :config="configClosed" />
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { apolloClient } from "@/vue-apollo";
|
||||
import { DefaultApolloClient } from "@vue/apollo-composable";
|
||||
import { reactive } from "vue";
|
||||
import UnloggedIntroduction from "./UnloggedIntroduction.vue";
|
||||
|
||||
function setupApp({ app }) {
|
||||
app.provide(DefaultApolloClient, apolloClient);
|
||||
}
|
||||
const config = reactive({
|
||||
name: "My instance name",
|
||||
description: "An instance that doesn't exist",
|
||||
slogan: "Test! Test! Test!",
|
||||
registrationsOpen: true,
|
||||
});
|
||||
|
||||
const configClosed = reactive({ ...config, registrationsOpen: false });
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<section v-if="config" class="container mx-auto px-2 my-3">
|
||||
<section class="container mx-auto px-2 my-3">
|
||||
<h1 class="dark:text-white font-bold">
|
||||
{{ config.slogan || t("Gather ⋅ Organize ⋅ Mobilize") }}
|
||||
{{ config.slogan ?? t("Gather ⋅ Organize ⋅ Mobilize") }}
|
||||
</h1>
|
||||
<i18n-t
|
||||
keypath="Join {instance}, a Mobilizon instance"
|
||||
@@ -37,16 +37,11 @@
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { CONFIG } from "@/graphql/config";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import { computed } from "vue";
|
||||
import RouteName from "@/router/name";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { result: configResult } = useQuery<{ config: IConfig }>(CONFIG);
|
||||
|
||||
const config = computed(() => configResult.value?.config);
|
||||
defineProps<{ config: IConfig }>();
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative pt-10">
|
||||
<div class="relative pt-10 px-2">
|
||||
<div class="w-full flex flex-wrap gap-3 mb-2 items-center">
|
||||
<h1
|
||||
class="text-xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
|
||||
@@ -9,7 +9,7 @@
|
||||
<button
|
||||
v-if="suggestGeoloc && isIPLocation"
|
||||
class="inline-flex bg-primary rounded text-white flex-initial px-4 py-2 justify-center w-full md:w-min whitespace-nowrap"
|
||||
@click="$emit('doGeoLoc')"
|
||||
@click="emit('doGeoLoc')"
|
||||
>
|
||||
{{ t("Geolocate me") }}
|
||||
</button>
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
<div
|
||||
class="relative w-full flex gap-6 snap-x overflow-x-auto pb-6"
|
||||
class="relative w-full snap-x overflow-x-auto pb-6 grid auto-rows-[1fr] gap-x-5 gap-y-8 grid-cols-[repeat(auto-fill,_minmax(250px,_1fr))]"
|
||||
ref="scrollContainer"
|
||||
@scroll="scrollHandler"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<router-link
|
||||
:to="to"
|
||||
class="mbz-card flex flex-col items-center dark:border-gray-700 shadow-md md:flex-col my-4 snap-center shrink-0 first:pl-8 w-[18rem]"
|
||||
class="mbz-card flex flex-col items-center dark:border-gray-700 shadow-md md:flex-col snap-center shrink-0 first:pl-8 w-[18rem]"
|
||||
>
|
||||
<div class="relative w-full group">
|
||||
<img
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<template>
|
||||
<nav class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-gray-900">
|
||||
<div
|
||||
class="container mx-auto flex flex-wrap justify-between items-center mx-auto"
|
||||
>
|
||||
<div class="container mx-auto flex flex-wrap items-center mx-auto gap-4">
|
||||
<router-link :to="{ name: RouteName.HOME }" class="flex items-center">
|
||||
<MobilizonLogo class="w-40" />
|
||||
</router-link>
|
||||
<div class="flex items-center md:order-2" v-if="currentActor?.id">
|
||||
<div class="flex items-center md:order-2 ml-auto" v-if="currentActor?.id">
|
||||
<o-dropdown>
|
||||
<template #trigger>
|
||||
<button
|
||||
@@ -80,7 +78,7 @@
|
||||
</o-dropdown>
|
||||
</div>
|
||||
<button
|
||||
@click="showMobileMenu = true"
|
||||
@click="showMobileMenu = !showMobileMenu"
|
||||
type="button"
|
||||
class="inline-flex items-center p-2 ml-1 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
||||
aria-controls="mobile-menu-2"
|
||||
@@ -109,6 +107,20 @@
|
||||
<ul
|
||||
class="flex flex-col md:flex-row md:space-x-8 mt-2 md:mt-0 md:text-sm md:font-medium"
|
||||
>
|
||||
<li v-if="currentActor?.id">
|
||||
<router-link
|
||||
:to="{ name: RouteName.MY_EVENTS }"
|
||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>{{ t("My events") }}</router-link
|
||||
>
|
||||
</li>
|
||||
<li v-if="currentActor?.id">
|
||||
<router-link
|
||||
:to="{ name: RouteName.MY_GROUPS }"
|
||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>{{ t("My groups") }}</router-link
|
||||
>
|
||||
</li>
|
||||
<li v-if="!currentActor?.id">
|
||||
<router-link
|
||||
:to="{ name: RouteName.LOGIN }"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { PostVisibility } from "@/types/enums";
|
||||
import { IPost } from "../../types/post.model";
|
||||
import RouteName from "@/router/name";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ShareModal from "@/components/Share/ShareModal.vue";
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</o-tooltip>
|
||||
</p>
|
||||
</o-field>
|
||||
<div class="flex">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<a
|
||||
:href="twitterShare"
|
||||
target="_blank"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center mt-80"
|
||||
:class="{ 'mt-40 mb-10': inline, 'text-center': center }"
|
||||
class="flex flex-col items-center"
|
||||
:class="{ 'mt-20 mb-10': inline, 'mt-80': !inline, 'text-center': center }"
|
||||
role="note"
|
||||
>
|
||||
<o-icon :icon="icon" customSize="48" />
|
||||
|
||||
Reference in New Issue
Block a user