2020-08-27 11:53:24 +02:00
< template >
< div v-if ="group" class="section" >
2022-01-10 15:19:16 +01:00
< breadcrumbs -nav
: links = " [
2022-07-12 10:55:28 +02:00
{ name : RouteName . ADMIN , text : t ( 'Admin' ) } ,
2022-01-10 15:19:16 +01:00
{
name : RouteName . ADMIN _GROUPS ,
2022-07-12 10:55:28 +02:00
text : t ( 'Groups' ) ,
2022-01-10 15:19:16 +01:00
} ,
{
name : RouteName . PROFILES ,
params : { id : group . id } ,
text : displayName ( group ) ,
} ,
] "
/ >
2022-04-22 12:00:47 +02:00
< div >
< p v-if ="group.suspended" class="mx-auto max-w-sm block mb-2" >
2021-09-30 09:25:44 +02:00
< actor -card
: actor = "group"
: full = "true"
: popover = "false"
: limit = "false"
/ >
< / p >
2020-08-27 11:53:24 +02:00
< router -link
2022-04-22 12:00:47 +02:00
class = "mx-auto max-w-sm block mb-2"
2021-09-30 09:25:44 +02:00
v - else
2020-11-30 10:24:11 +01:00
: to = " {
name : RouteName . GROUP ,
params : { preferredUsername : usernameWithDomain ( group ) } ,
} "
2020-08-27 11:53:24 +02:00
>
2020-11-30 10:24:11 +01:00
< actor -card
: actor = "group"
: full = "true"
: popover = "false"
: limit = "false"
/ >
2020-08-27 11:53:24 +02:00
< / r o u t e r - l i n k >
< / div >
2022-07-12 10:55:28 +02:00
< table v-if ="metadata.length > 0" class="table w-full" >
2020-08-27 11:53:24 +02:00
< tbody >
< tr v-for ="{ key, value, link } in metadata" :key ="key" >
< td > { { key } } < / td >
< td v-if ="link" >
< router -link :to ="link" >
{ { value } }
< / r o u t e r - l i n k >
< / td >
< td v-else > {{ value }} < / td >
< / tr >
< / tbody >
< / table >
2022-07-12 10:55:28 +02:00
< div class = "flex gap-1" >
< o -button
2020-11-30 10:24:11 +01:00
@ click = "confirmSuspendProfile"
v - if = "!group.suspended"
2022-07-12 10:55:28 +02:00
variant = "primary"
> { { t ( "Suspend" ) } } < / o - b u t t o n
2020-11-30 10:24:11 +01:00
>
2022-07-12 10:55:28 +02:00
< o -button
@ click = "
unsuspendProfile ( {
id ,
} )
"
2020-11-30 10:24:11 +01:00
v - if = "group.suspended"
2022-07-12 10:55:28 +02:00
variant = "primary"
> { { t ( "Unsuspend" ) } } < / o - b u t t o n
2020-11-30 10:24:11 +01:00
>
2022-07-12 10:55:28 +02:00
< o -button
@ click = "
refreshProfile ( {
actorId : id ,
} )
"
2020-11-30 10:24:11 +01:00
v - if = "group.domain"
2022-07-12 10:55:28 +02:00
variant = "primary"
2020-11-30 10:24:11 +01:00
outlined
2022-07-12 10:55:28 +02:00
> { { t ( "Refresh profile" ) } } < / o - b u t t o n
2020-11-30 10:24:11 +01:00
>
2020-08-27 11:53:24 +02:00
< / div >
< section >
2022-07-12 10:55:28 +02:00
< h2 >
2020-08-27 11:53:24 +02:00
{ {
2022-07-12 10:55:28 +02:00
t (
"{number} members" ,
{
number : group . members . total ,
} ,
group . members . total
)
2020-08-27 11:53:24 +02:00
} }
< / h2 >
2022-07-12 10:55:28 +02:00
< o -table
2020-08-27 11:53:24 +02:00
: data = "group.members.elements"
2022-07-12 10:55:28 +02:00
: loading = "loading"
2020-08-27 11:53:24 +02:00
paginated
backend - pagination
2022-07-12 10:55:28 +02:00
v - model : current - page = "membersPage"
: aria - next - label = "t('Next page')"
: aria - previous - label = "t('Previous page')"
: aria - page - label = "t('Page')"
: aria - current - label = "t('Current page')"
2020-08-27 11:53:24 +02:00
: total = "group.members.total"
2021-12-13 17:39:02 +01:00
: per - page = "MEMBERS_PER_PAGE"
2020-08-27 11:53:24 +02:00
@ page - change = "onMembersPageChange"
>
2022-07-12 10:55:28 +02:00
< o -table -column
2020-11-30 10:24:11 +01:00
field = "actor.preferredUsername"
2022-07-12 10:55:28 +02:00
: label = "t('Member')"
2020-11-30 10:24:11 +01:00
v - slot = "props"
>
2022-08-26 16:08:58 +02:00
< article class = "flex gap-1" >
< div class = "flex-none" >
< figure v-if ="props.row.actor.avatar" >
< img
class = "rounded"
: src = "props.row.actor.avatar.url"
alt = ""
width = "48"
height = "48"
/ >
< / figure >
< AccountCircle :size ="48" v -else / >
< / div >
< div >
2022-07-12 10:55:28 +02:00
< div class = "prose dark:prose-invert" >
2020-11-30 10:24:11 +01:00
< span v-if ="props.row.actor.name" > {{
props . row . actor . name
} } < / s p a n
2021-05-12 18:10:07 +02:00
> < span v-else > @ {{ usernameWithDomain ( props.row.actor ) }} < / span
2020-08-27 11:53:24 +02:00
> < br / >
2022-08-26 16:08:58 +02:00
< span v -if = " props.row.actor.name "
2020-08-27 11:53:24 +02:00
> @ { { usernameWithDomain ( props . row . actor ) } } < / s p a n
>
< / div >
< / div >
< / article >
2022-07-12 10:55:28 +02:00
< / o - t a b l e - c o l u m n >
< o -table -column field = "role" :label ="t('Role')" v-slot ="props" >
2022-08-26 16:08:58 +02:00
< tag
2022-07-12 10:55:28 +02:00
variant = "primary"
2020-11-30 10:24:11 +01:00
v - if = "props.row.role === MemberRole.ADMINISTRATOR"
>
2022-07-12 10:55:28 +02:00
{ { t ( "Administrator" ) } }
2022-08-26 16:08:58 +02:00
< / tag >
< tag
2022-07-12 10:55:28 +02:00
variant = "primary"
2020-11-30 10:24:11 +01:00
v - else - if = "props.row.role === MemberRole.MODERATOR"
>
2022-07-12 10:55:28 +02:00
{ { t ( "Moderator" ) } }
2022-08-26 16:08:58 +02:00
< / tag >
< tag v -else -if = " props.row.role = = = MemberRole.MEMBER " >
2022-07-12 10:55:28 +02:00
{ { t ( "Member" ) } }
2022-08-26 16:08:58 +02:00
< / tag >
< tag
2022-07-12 10:55:28 +02:00
variant = "warning"
2020-11-30 10:24:11 +01:00
v - else - if = "props.row.role === MemberRole.NOT_APPROVED"
>
2022-07-12 10:55:28 +02:00
{ { t ( "Not approved" ) } }
2022-08-26 16:08:58 +02:00
< / tag >
< tag
2022-07-12 10:55:28 +02:00
variant = "danger"
2020-11-30 10:24:11 +01:00
v - else - if = "props.row.role === MemberRole.REJECTED"
>
2022-07-12 10:55:28 +02:00
{ { t ( "Rejected" ) } }
2022-08-26 16:08:58 +02:00
< / tag >
< tag
2022-07-12 10:55:28 +02:00
variant = "danger"
2020-11-30 10:24:11 +01:00
v - else - if = "props.row.role === MemberRole.INVITED"
>
2022-07-12 10:55:28 +02:00
{ { t ( "Invited" ) } }
2022-08-26 16:08:58 +02:00
< / tag >
2022-07-12 10:55:28 +02:00
< / o - t a b l e - c o l u m n >
< o -table -column field = "insertedAt" :label ="t('Date')" v-slot ="props" >
2020-08-27 11:53:24 +02:00
< span class = "has-text-centered" >
2022-07-12 10:55:28 +02:00
{ { formatDateString ( props . row . insertedAt ) } } < br / > { {
formatTimeString ( props . row . insertedAt )
2020-08-27 11:53:24 +02:00
} }
< / span >
2022-07-12 10:55:28 +02:00
< / o - t a b l e - c o l u m n >
< template # empty >
2021-05-12 18:10:07 +02:00
< empty -content icon = "account-group" :inline ="true" >
2022-07-12 10:55:28 +02:00
{ { t ( "No members found" ) } }
2021-05-12 18:10:07 +02:00
< / e m p t y - c o n t e n t >
2020-08-27 11:53:24 +02:00
< / template >
2022-07-12 10:55:28 +02:00
< / o - t a b l e >
2020-08-27 11:53:24 +02:00
< / section >
< section >
2022-07-12 10:55:28 +02:00
< h2 >
2020-08-27 11:53:24 +02:00
{ {
2022-07-12 10:55:28 +02:00
t (
"{number} organized events" ,
{
number : group . organizedEvents . total ,
} ,
group . organizedEvents . total
)
2020-08-27 11:53:24 +02:00
} }
< / h2 >
2022-07-12 10:55:28 +02:00
< o -table
2020-08-27 11:53:24 +02:00
: data = "group.organizedEvents.elements"
2022-07-12 10:55:28 +02:00
: loading = "loading"
2020-08-27 11:53:24 +02:00
paginated
backend - pagination
2022-07-12 10:55:28 +02:00
v - model : current - page = "organizedEventsPage"
: aria - next - label = "t('Next page')"
: aria - previous - label = "t('Previous page')"
: aria - page - label = "t('Page')"
: aria - current - label = "t('Current page')"
2020-08-27 11:53:24 +02:00
: total = "group.organizedEvents.total"
: per - page = "EVENTS_PER_PAGE"
@ page - change = "onOrganizedEventsPageChange"
>
2022-07-12 10:55:28 +02:00
< o -table -column field = "title" :label ="t('Title')" v-slot ="props" >
2020-11-30 10:24:11 +01:00
< router -link
: to = "{ name: RouteName.EVENT, params: { uuid: props.row.uuid } }"
>
2020-08-27 11:53:24 +02:00
{ { props . row . title } }
2022-08-26 16:08:58 +02:00
< tag variant = "info" v-if ="props.row.draft" > {{ t ( " Draft " ) }} < / tag >
2020-08-27 11:53:24 +02:00
< / r o u t e r - l i n k >
2022-07-12 10:55:28 +02:00
< / o - t a b l e - c o l u m n >
< o -table -column field = "beginsOn" : label = "t('Begins on')" v-slot ="props" >
{ { formatDateTimeString ( props . row . beginsOn ) } }
< / o - t a b l e - c o l u m n >
< template # empty >
2021-05-12 18:10:07 +02:00
< empty -content icon = "account-group" :inline ="true" >
2022-07-12 10:55:28 +02:00
{ { t ( "No organized events found" ) } }
2021-05-12 18:10:07 +02:00
< / e m p t y - c o n t e n t >
2020-08-27 11:53:24 +02:00
< / template >
2022-07-12 10:55:28 +02:00
< / o - t a b l e >
2020-08-27 11:53:24 +02:00
< / section >
< section >
2022-07-12 10:55:28 +02:00
< h2 >
2020-08-27 11:53:24 +02:00
{ {
2022-07-12 10:55:28 +02:00
t (
"{number} posts" ,
{
number : group . posts . total ,
} ,
group . posts . total
)
2020-08-27 11:53:24 +02:00
} }
< / h2 >
2022-07-12 10:55:28 +02:00
< o -table
2020-08-27 11:53:24 +02:00
: data = "group.posts.elements"
2022-07-12 10:55:28 +02:00
: loading = "loading"
2020-08-27 11:53:24 +02:00
paginated
backend - pagination
2022-07-12 10:55:28 +02:00
v - model : current - page = "postsPage"
: aria - next - label = "t('Next page')"
: aria - previous - label = "t('Previous page')"
: aria - page - label = "t('Page')"
: aria - current - label = "t('Current page')"
2020-08-27 11:53:24 +02:00
: total = "group.posts.total"
2021-05-12 18:10:07 +02:00
: per - page = "POSTS_PER_PAGE"
2020-08-27 11:53:24 +02:00
@ page - change = "onPostsPageChange"
>
2022-07-12 10:55:28 +02:00
< o -table -column field = "title" :label ="t('Title')" v-slot ="props" >
2020-11-30 10:24:11 +01:00
< router -link
: to = "{ name: RouteName.POST, params: { slug: props.row.slug } }"
>
2020-08-27 11:53:24 +02:00
{ { props . row . title } }
2022-08-26 16:08:58 +02:00
< tag variant = "info" v-if ="props.row.draft" > {{ t ( " Draft " ) }} < / tag >
2020-08-27 11:53:24 +02:00
< / r o u t e r - l i n k >
2022-07-12 10:55:28 +02:00
< / o - t a b l e - c o l u m n >
< o -table -column
2020-11-30 10:24:11 +01:00
field = "publishAt"
2022-07-12 10:55:28 +02:00
: label = "t('Publication date')"
2020-11-30 10:24:11 +01:00
v - slot = "props"
>
2022-07-12 10:55:28 +02:00
{ { formatDateTimeString ( props . row . publishAt ) } }
< / o - t a b l e - c o l u m n >
< template # empty >
2021-05-12 18:10:07 +02:00
< empty -content icon = "bullhorn" :inline ="true" >
2022-07-12 10:55:28 +02:00
{ { t ( "No posts found" ) } }
2021-05-12 18:10:07 +02:00
< / e m p t y - c o n t e n t >
2020-08-27 11:53:24 +02:00
< / template >
2022-07-12 10:55:28 +02:00
< / o - t a b l e >
2020-08-27 11:53:24 +02:00
< / section >
< / div >
2022-07-12 10:55:28 +02:00
< empty -content v -else -if = " ! loading " icon = "account-multiple" >
{ { t ( "This group was not found" ) } }
2021-12-13 17:33:10 +01:00
< template # desc >
2022-07-12 10:55:28 +02:00
< o -button
2022-08-26 16:08:58 +02:00
variant = "text"
2021-12-13 17:33:10 +01:00
tag = "router-link"
: to = "{ name: RouteName.ADMIN_GROUPS }"
2022-07-12 10:55:28 +02:00
> { { t ( "Back to group list" ) } } < / o - b u t t o n
2021-12-13 17:33:10 +01:00
>
< / template >
< / e m p t y - c o n t e n t >
2020-08-27 11:53:24 +02:00
< / template >
2022-07-12 10:55:28 +02:00
< script lang = "ts" setup >
2020-08-31 12:40:30 +02:00
import { GET _GROUP , REFRESH _PROFILE } from "@/graphql/group" ;
2020-11-23 16:58:50 +01:00
import { formatBytes } from "@/utils/datetime" ;
2020-11-27 19:27:44 +01:00
import { MemberRole } from "@/types/enums" ;
2020-08-27 11:53:24 +02:00
import { SUSPEND _PROFILE , UNSUSPEND _PROFILE } from "../../graphql/actor" ;
2020-11-27 19:27:44 +01:00
import { IGroup } from "../../types/actor" ;
2022-01-10 15:19:16 +01:00
import {
usernameWithDomain ,
displayName ,
IActor ,
} from "../../types/actor/actor.model" ;
2020-08-27 11:53:24 +02:00
import RouteName from "../../router/name" ;
import ActorCard from "../../components/Account/ActorCard.vue" ;
2021-05-12 18:10:07 +02:00
import EmptyContent from "../../components/Utils/EmptyContent.vue" ;
2021-06-11 14:21:27 +02:00
import { ApolloCache , FetchResult } from "@apollo/client/core" ;
2022-07-12 10:55:28 +02:00
import { useMutation , useQuery } from "@vue/apollo-composable" ;
import { computed , inject } from "vue" ;
import { useHead } from "@vueuse/head" ;
import { integerTransformer , useRouteQuery } from "vue-use-route-query" ;
import { useI18n } from "vue-i18n" ;
import {
formatTimeString ,
formatDateString ,
formatDateTimeString ,
} from "@/filters/datetime" ;
import { Dialog } from "@/plugins/dialog" ;
import { Notifier } from "@/plugins/notifier" ;
2022-08-26 16:08:58 +02:00
import AccountCircle from "vue-material-design-icons/AccountCircle.vue" ;
2022-09-20 16:53:26 +02:00
import Tag from "@/components/TagElement.vue" ;
2020-08-27 11:53:24 +02:00
2021-05-25 16:22:01 +02:00
const EVENTS _PER _PAGE = 10 ;
const POSTS _PER _PAGE = 10 ;
const MEMBERS _PER _PAGE = 10 ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const props = defineProps < { id : string } > ( ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const organizedEventsPage = useRouteQuery (
"organizedEventsPage" ,
1 ,
integerTransformer
) ;
const membersPage = useRouteQuery ( "membersPage" , 1 , integerTransformer ) ;
const postsPage = useRouteQuery ( "postsPage" , 1 , integerTransformer ) ;
2022-01-10 15:19:16 +01:00
2022-07-12 10:55:28 +02:00
const {
result : groupResult ,
loading ,
fetchMore ,
} = useQuery (
GET _GROUP ,
( ) => ( {
id : props . id ,
organizedEventsPage : organizedEventsPage . value ,
organizedEventsLimit : EVENTS _PER _PAGE ,
postsPage : postsPage . value ,
postsLimit : POSTS _PER _PAGE ,
membersLimit : MEMBERS _PER _PAGE ,
membersPage : membersPage . value ,
} ) ,
( ) => ( {
enabled : props . id !== undefined ,
} )
) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const group = computed ( ( ) => groupResult . value ? . getGroup ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const { t } = useI18n ( { useScope : "global" } ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
useHead ( {
title : computed ( ( ) => displayName ( group . value ) ) ,
} ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const metadata = computed ( ( ) : Array < Record < string , string > > => {
if ( ! group . value ) return [ ] ;
const res : Record < string , string > [ ] = [
{
key : t ( "Status" ) as string ,
value : ( group . value . suspended ? t ( "Suspended" ) : t ( "Active" ) ) as string ,
} ,
{
key : t ( "Domain" ) as string ,
value : ( group . value . domain ? group . value . domain : t ( "Local" ) ) as string ,
} ,
{
key : t ( "Uploaded media size" ) as string ,
value : formatBytes ( group . value . mediaSize ) ,
} ,
] ;
return res ;
} ) ;
2021-05-12 18:10:07 +02:00
2022-07-12 10:55:28 +02:00
const dialog = inject < Dialog > ( "dialog" ) ;
const notifier = inject < Notifier > ( "notifier" ) ;
2021-05-12 18:10:07 +02:00
2022-07-12 10:55:28 +02:00
const confirmSuspendProfile = ( ) : void => {
2022-08-26 16:08:58 +02:00
const message = group . value . domain
? t (
"Are you sure you want to <b>suspend</b> this group? As this group originates from instance {instance}, this will only remove local members and delete the local data, as well as rejecting all the future data." ,
{ instance : group . value . domain }
)
: t (
"Are you sure you want to <b>suspend</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>."
) ;
2021-05-12 18:10:07 +02:00
2022-07-12 10:55:28 +02:00
dialog ? . confirm ( {
2022-08-26 16:08:58 +02:00
title : t ( "Suspend group" ) ,
2022-07-12 10:55:28 +02:00
message ,
2022-08-26 16:08:58 +02:00
confirmText : t ( "Suspend group" ) ,
cancelText : t ( "Cancel" ) ,
variant : "danger" ,
2022-07-12 10:55:28 +02:00
hasIcon : true ,
onConfirm : ( ) =>
suspendProfile ( {
id : props . id ,
} ) ,
} ) ;
} ;
2021-05-12 18:10:07 +02:00
2022-07-12 10:55:28 +02:00
const { mutate : suspendProfile , onError : onSuspendProfileError } = useMutation < {
suspendProfile : { id : string } ;
} > ( SUSPEND _PROFILE , ( ) => ( {
update : (
store : ApolloCache < { suspendProfile : { id : string } } > ,
{ data } : FetchResult
) => {
if ( data == null ) return ;
const profileId = props . id ;
2021-05-12 18:10:07 +02:00
2022-07-12 10:55:28 +02:00
const profileData = store . readQuery < { getGroup : IGroup } > ( {
query : GET _GROUP ,
variables : {
id : profileId ,
organizedEventsPage : organizedEventsPage . value ,
organizedEventsLimit : EVENTS _PER _PAGE ,
postsPage : postsPage . value ,
postsLimit : POSTS _PER _PAGE ,
2020-08-27 11:53:24 +02:00
} ,
2022-07-12 10:55:28 +02:00
} ) ;
if ( ! profileData ) return ;
store . writeQuery ( {
query : GET _GROUP ,
variables : {
id : profileId ,
2020-08-27 11:53:24 +02:00
} ,
2022-07-12 10:55:28 +02:00
data : {
getGroup : {
... profileData . getGroup ,
suspended : true ,
avatar : null ,
name : "" ,
summary : "" ,
} ,
2020-11-23 16:58:50 +01:00
} ,
2020-08-27 11:53:24 +02:00
} ) ;
2022-07-12 10:55:28 +02:00
} ,
} ) ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
onSuspendProfileError ( ( e ) => {
console . error ( e ) ;
notifier ? . error ( t ( "Error while suspending group" ) ) ;
} ) ;
const { mutate : unsuspendProfile , onError : onUnsuspendProfileError } =
useMutation ( UNSUSPEND _PROFILE , ( ) => ( {
refetchQueries : [
{
query : GET _GROUP ,
2021-05-12 18:10:07 +02:00
variables : {
2022-07-12 10:55:28 +02:00
id : props . id ,
2021-05-12 18:10:07 +02:00
} ,
2022-07-12 10:55:28 +02:00
} ,
] ,
} ) ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
onUnsuspendProfileError ( ( e ) => {
console . error ( e ) ;
notifier ? . error ( t ( "Error while suspending group" ) ) ;
} ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const {
mutate : refreshProfile ,
onDone : onRefreshProfileDone ,
onError : onRefreshProfileError ,
} = useMutation < { refreshProfile : IActor } > ( REFRESH _PROFILE ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
onRefreshProfileDone ( ( ) => {
notifier ? . success ( t ( "Triggered profile refreshment" ) ) ;
} ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
onRefreshProfileError ( ( e ) => {
console . error ( e ) ;
notifier ? . error ( t ( "Error while suspending group" ) ) ;
} ) ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const onOrganizedEventsPageChange = async ( page : number ) : Promise < void > => {
organizedEventsPage . value = page ;
await fetchMore ( {
variables : {
id : props . id ,
organizedEventsPage : organizedEventsPage . value ,
organizedEventsLimit : EVENTS _PER _PAGE ,
} ,
} ) ;
} ;
2020-08-27 11:53:24 +02:00
2022-07-12 10:55:28 +02:00
const onMembersPageChange = async ( page : number ) : Promise < void > => {
membersPage . value = page ;
await fetchMore ( {
variables : {
id : props . id ,
membersPage : membersPage . value ,
membersLimit : EVENTS _PER _PAGE ,
} ,
} ) ;
} ;
2021-05-12 18:10:07 +02:00
2022-07-12 10:55:28 +02:00
const onPostsPageChange = async ( page : number ) : Promise < void > => {
postsPage . value = page ;
await fetchMore ( {
variables : {
id : props . id ,
postsPage : postsPage . value ,
postsLimit : POSTS _PER _PAGE ,
} ,
} ) ;
} ;
2020-08-27 11:53:24 +02:00
< / script >