Introduce the group activity section
Signed-off-by: Thomas Citharel <tcit@tcit.fr>chapril-v3
parent
d0567f783d
commit
3fe64a4389
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<b-icon :icon="'chat'" :type="iconColor" />
|
||||
<div class="subject">
|
||||
<i18n :path="translation" tag="p">
|
||||
<router-link
|
||||
v-if="activity.object"
|
||||
slot="discussion"
|
||||
:to="{
|
||||
name: RouteName.DISCUSSION,
|
||||
params: { slug: subjectParams.discussion_slug },
|
||||
}"
|
||||
>{{ subjectParams.discussion_title }}</router-link
|
||||
>
|
||||
<b v-else slot="discussion">{{ subjectParams.discussion_title }}</b>
|
||||
<router-link
|
||||
v-if="activity.object && subjectParams.old_discussion_title"
|
||||
slot="old_discussion"
|
||||
:to="{
|
||||
name: RouteName.DISCUSSION,
|
||||
params: { slug: subjectParams.discussion_slug },
|
||||
}"
|
||||
>{{ subjectParams.old_discussion_title }}</router-link
|
||||
>
|
||||
<b
|
||||
v-else-if="subjectParams.old_discussion_title"
|
||||
slot="old_discussion"
|
||||
>{{ subjectParams.old_discussion_title }}</b
|
||||
>
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<small class="has-text-grey activity-date">{{
|
||||
activity.insertedAt | formatTimeString
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { ActivityDiscussionSubject } from "@/types/enums";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
import ActivityMixin from "../../mixins/activity";
|
||||
import { mixins } from "vue-class-component";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PopoverActorCard,
|
||||
},
|
||||
})
|
||||
export default class DiscussionActivityItem extends mixins(ActivityMixin) {
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
RouteName = RouteName;
|
||||
ActivityDiscussionSubject = ActivityDiscussionSubject;
|
||||
|
||||
get translation(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityDiscussionSubject.DISCUSSION_CREATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You created the discussion {discussion}.";
|
||||
}
|
||||
return "{profile} created the discussion {discussion}.";
|
||||
case ActivityDiscussionSubject.DISCUSSION_REPLIED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You replied to the discussion {discussion}.";
|
||||
}
|
||||
return "{profile} replied to the discussion {discussion}.";
|
||||
case ActivityDiscussionSubject.DISCUSSION_RENAMED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You renamed the discussion from {old_discussion} to {discussion}.";
|
||||
}
|
||||
return "{profile} renamed the discussion from {old_discussion} to {discussion}.";
|
||||
case ActivityDiscussionSubject.DISCUSSION_ARCHIVED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You archived the discussion {discussion}.";
|
||||
}
|
||||
return "{profile} archived the discussion {discussion}.";
|
||||
case ActivityDiscussionSubject.DISCUSSION_DELETED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You deleted the discussion {discussion}.";
|
||||
}
|
||||
return "{profile} deleted the discussion {discussion}.";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get iconColor(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityDiscussionSubject.DISCUSSION_CREATED:
|
||||
case ActivityDiscussionSubject.DISCUSSION_REPLIED:
|
||||
return "is-success";
|
||||
case ActivityDiscussionSubject.DISCUSSION_RENAMED:
|
||||
case ActivityDiscussionSubject.DISCUSSION_ARCHIVED:
|
||||
return "is-grey";
|
||||
case ActivityDiscussionSubject.DISCUSSION_DELETED:
|
||||
return "is-danger";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
</style>
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<b-icon :icon="'calendar'" :type="iconColor" />
|
||||
<div class="subject">
|
||||
<i18n :path="translation" tag="p">
|
||||
<router-link
|
||||
slot="event"
|
||||
v-if="activity.object"
|
||||
:to="{
|
||||
name: RouteName.EVENT,
|
||||
params: { uuid: subjectParams.event_uuid },
|
||||
}"
|
||||
>{{ subjectParams.event_title }}</router-link
|
||||
>
|
||||
<b v-else slot="event">{{ subjectParams.event_title }}</b>
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<small class="has-text-grey activity-date">{{
|
||||
activity.insertedAt | formatTimeString
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { ActivityEventSubject } from "@/types/enums";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
import ActivityMixin from "../../mixins/activity";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PopoverActorCard,
|
||||
},
|
||||
})
|
||||
export default class EventActivityItem extends mixins(ActivityMixin) {
|
||||
ActivityEventSubject = ActivityEventSubject;
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
RouteName = RouteName;
|
||||
|
||||
get translation(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityEventSubject.EVENT_CREATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You created the event {event}.";
|
||||
}
|
||||
return "The event {event} was created by {profile}.";
|
||||
case ActivityEventSubject.EVENT_UPDATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You updated the event {event}.";
|
||||
}
|
||||
return "The event {event} was updated by {profile}.";
|
||||
case ActivityEventSubject.EVENT_DELETED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You deleted the event {event}.";
|
||||
}
|
||||
return "The event {event} was deleted by {profile}.";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get iconColor(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityEventSubject.EVENT_CREATED:
|
||||
return "is-success";
|
||||
case ActivityEventSubject.EVENT_UPDATED:
|
||||
return "is-grey";
|
||||
case ActivityEventSubject.EVENT_DELETED:
|
||||
return "is-danger";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
</style>
|
@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<b-icon :icon="'cog'" :type="iconColor" />
|
||||
<div class="subject">
|
||||
<i18n :path="translation" tag="p">
|
||||
<router-link
|
||||
v-if="activity.object"
|
||||
slot="group"
|
||||
:to="{
|
||||
name: RouteName.GROUP,
|
||||
params: { preferredUsername: usernameWithDomain(activity.object) },
|
||||
}"
|
||||
>{{ subjectParams.group_name }}</router-link
|
||||
>
|
||||
<b v-else slot="post">{{ subjectParams.group_name }}</b>
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<i18n
|
||||
:path="detail"
|
||||
v-for="detail in details"
|
||||
:key="detail"
|
||||
tag="p"
|
||||
class="has-text-grey"
|
||||
>
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
>
|
||||
<router-link
|
||||
v-if="activity.object"
|
||||
slot="group"
|
||||
:to="{
|
||||
name: RouteName.GROUP,
|
||||
params: { preferredUsername: usernameWithDomain(activity.object) },
|
||||
}"
|
||||
>{{ subjectParams.group_name }}</router-link
|
||||
>
|
||||
<b v-else slot="post">{{ subjectParams.group_name }}</b>
|
||||
<b v-if="subjectParams.old_group_name" slot="old_group_name">{{
|
||||
subjectParams.old_group_name
|
||||
}}</b>
|
||||
</i18n>
|
||||
<small class="has-text-grey activity-date">{{
|
||||
activity.insertedAt | formatTimeString
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { ActivityGroupSubject, GroupVisibility, Openness } from "@/types/enums";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
import ActivityMixin from "../../mixins/activity";
|
||||
import { mixins } from "vue-class-component";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PopoverActorCard,
|
||||
},
|
||||
})
|
||||
export default class GroupActivityItem extends mixins(ActivityMixin) {
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
RouteName = RouteName;
|
||||
ActivityGroupSubject = ActivityGroupSubject;
|
||||
|
||||
get translation(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityGroupSubject.GROUP_CREATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You created the group {group}.";
|
||||
}
|
||||
return "{profile} created the group {group}.";
|
||||
case ActivityGroupSubject.GROUP_UPDATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You updated the group {group}.";
|
||||
}
|
||||
return "{profile} updated the group {group}.";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get iconColor(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityGroupSubject.GROUP_CREATED:
|
||||
return "is-success";
|
||||
case ActivityGroupSubject.GROUP_UPDATED:
|
||||
return "is-grey";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get details(): string[] {
|
||||
let details = [];
|
||||
const changes = this.subjectParams.group_changes.split(",");
|
||||
if (changes.includes("name") && this.subjectParams.old_group_name) {
|
||||
details.push("{old_group_name} was renamed to {group}.");
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (changes.includes("visibility") && this.activity.object.visibility) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
switch (this.activity.object.visibility) {
|
||||
case GroupVisibility.PRIVATE:
|
||||
details.push("Visibility was set to private.");
|
||||
break;
|
||||
case GroupVisibility.PUBLIC:
|
||||
details.push("Visibility was set to public.");
|
||||
break;
|
||||
default:
|
||||
details.push("Visibility was set to an unknown value.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (changes.includes("openness") && this.activity.object.openness) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
switch (this.activity.object.openness) {
|
||||
case Openness.INVITE_ONLY:
|
||||
details.push("The group can now only be joined with an invite.");
|
||||
break;
|
||||
case Openness.OPEN:
|
||||
details.push("The group can now be joined by anyone.");
|
||||
break;
|
||||
default:
|
||||
details.push("Unknown value for the openness setting.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (changes.includes("address") && this.activity.object.physicalAddress) {
|
||||
details.push("The group's physical address was changed.");
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (changes.includes("avatar") && this.activity.object.avatar) {
|
||||
details.push("The group's avatar was changed.");
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (changes.includes("banner") && this.activity.object.banner) {
|
||||
details.push("The group's banner was changed.");
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (changes.includes("summary") && this.activity.object.summary) {
|
||||
details.push("The group's short description was changed.");
|
||||
}
|
||||
return details;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
</style>
|
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<b-icon :icon="icon" :type="iconColor" />
|
||||
<div class="subject">
|
||||
<i18n :path="translation" tag="p">
|
||||
<popover-actor-card
|
||||
v-if="activity.object"
|
||||
:actor="activity.object.actor"
|
||||
:inline="true"
|
||||
slot="member"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.object.actor),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
>
|
||||
<b slot="member" v-else>{{
|
||||
subjectParams.member_preferred_username
|
||||
}}</b>
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<small class="has-text-grey activity-date">{{
|
||||
activity.insertedAt | formatTimeString
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { ActivityMemberSubject, MemberRole } from "@/types/enums";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
import ActivityMixin from "../../mixins/activity";
|
||||
import { mixins } from "vue-class-component";
|
||||
|
||||
export const MEMBER_ROLE_VALUE: Record<string, number> = {
|
||||
[MemberRole.MEMBER]: 20,
|
||||
[MemberRole.MODERATOR]: 50,
|
||||
[MemberRole.ADMINISTRATOR]: 90,
|
||||
[MemberRole.CREATOR]: 100,
|
||||
};
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PopoverActorCard,
|
||||
},
|
||||
})
|
||||
export default class MemberActivityItem extends mixins(ActivityMixin) {
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
RouteName = RouteName;
|
||||
ActivityMemberSubject = ActivityMemberSubject;
|
||||
|
||||
get translation(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityMemberSubject.MEMBER_REQUEST:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You requested to join the group.";
|
||||
}
|
||||
return "{member} requested to join the group.";
|
||||
case ActivityMemberSubject.MEMBER_INVITED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You invited {member}.";
|
||||
}
|
||||
return "{member} was invited by {profile}.";
|
||||
case ActivityMemberSubject.MEMBER_ADDED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You added the member {member}.";
|
||||
}
|
||||
return "{profile} added the member {member}.";
|
||||
case ActivityMemberSubject.MEMBER_UPDATED:
|
||||
if (this.subjectParams.member_role && this.subjectParams.old_role) {
|
||||
return this.roleUpdate;
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You updated the member {member}.";
|
||||
}
|
||||
return "{profile} updated the member {member}.";
|
||||
case ActivityMemberSubject.MEMBER_REMOVED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You excluded member {member}.";
|
||||
}
|
||||
return "{profile} excluded member {member}.";
|
||||
case ActivityMemberSubject.MEMBER_QUIT:
|
||||
return "{profile} quit the group.";
|
||||
case ActivityMemberSubject.MEMBER_REJECTED_INVITATION:
|
||||
return "{member} rejected the invitation to join the group.";
|
||||
case ActivityMemberSubject.MEMBER_ACCEPTED_INVITATION:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You accepted the invitation to join the group.";
|
||||
}
|
||||
return "{member} accepted the invitation to join the group.";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get icon(): string {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityMemberSubject.MEMBER_REQUEST:
|
||||
case ActivityMemberSubject.MEMBER_ADDED:
|
||||
case ActivityMemberSubject.MEMBER_INVITED:
|
||||
case ActivityMemberSubject.MEMBER_ACCEPTED_INVITATION:
|
||||
return "account-multiple-plus";
|
||||
case ActivityMemberSubject.MEMBER_REMOVED:
|
||||
case ActivityMemberSubject.MEMBER_REJECTED_INVITATION:
|
||||
case ActivityMemberSubject.MEMBER_QUIT:
|
||||
return "account-multiple-minus";
|
||||
case ActivityMemberSubject.MEMBER_UPDATED:
|
||||
default:
|
||||
return "account-multiple";
|
||||
}
|
||||
}
|
||||
|
||||
get iconColor(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityMemberSubject.MEMBER_ADDED:
|
||||
case ActivityMemberSubject.MEMBER_INVITED:
|
||||
case ActivityMemberSubject.MEMBER_JOINED:
|
||||
case ActivityMemberSubject.MEMBER_APPROVED:
|
||||
case ActivityMemberSubject.MEMBER_ACCEPTED_INVITATION:
|
||||
return "is-success";
|
||||
case ActivityMemberSubject.MEMBER_REQUEST:
|
||||
case ActivityMemberSubject.MEMBER_UPDATED:
|
||||
return "is-grey";
|
||||
case ActivityMemberSubject.MEMBER_REMOVED:
|
||||
case ActivityMemberSubject.MEMBER_REJECTED_INVITATION:
|
||||
case ActivityMemberSubject.MEMBER_QUIT:
|
||||
return "is-danger";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get roleUpdate(): string | undefined {
|
||||
if (
|
||||
Object.keys(MEMBER_ROLE_VALUE).includes(this.subjectParams.member_role) &&
|
||||
Object.keys(MEMBER_ROLE_VALUE).includes(this.subjectParams.old_role)
|
||||
) {
|
||||
if (
|
||||
MEMBER_ROLE_VALUE[this.subjectParams.member_role] >
|
||||
MEMBER_ROLE_VALUE[this.subjectParams.old_role]
|
||||
) {
|
||||
switch (this.subjectParams.member_role) {
|
||||
case MemberRole.MODERATOR:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You promoted {member} to moderator.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "You were promoted to moderator by {profile}.";
|
||||
}
|
||||
return "{profile} promoted {member} to moderator.";
|
||||
case MemberRole.ADMINISTRATOR:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You promoted {member} to administrator.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "You were promoted to administrator by {profile}.";
|
||||
}
|
||||
return "{profile} promoted {member} to administrator.";
|
||||
default:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You promoted the member {member} to an unknown role.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "You were promoted to an unknown role by {profile}.";
|
||||
}
|
||||
return "{profile} promoted {member} to an unknown role.";
|
||||
}
|
||||
} else {
|
||||
switch (this.subjectParams.member_role) {
|
||||
case MemberRole.MODERATOR:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You demoted {member} to moderator.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "You were demoted to moderator by {profile}.";
|
||||
}
|
||||
return "{profile} demoted {member} to moderator.";
|
||||
case MemberRole.MEMBER:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You demoted {member} to simple member.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "You were demoted to simple member by {profile}.";
|
||||
}
|
||||
return "{profile} demoted {member} to simple member.";
|
||||
default:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You demoted the member {member} to an unknown role.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "You were demoted to an unknown role by {profile}.";
|
||||
}
|
||||
return "{profile} demoted {member} to an unknown role.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You updated the member {member}.";
|
||||
}
|
||||
return "{profile} updated the member {member}";
|
||||
}
|
||||
}
|
||||
|
||||
get isObjectMemberCurrentActor(): boolean {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.activity?.object?.actor?.id === this.currentActor?.id &&
|
||||
this.currentActor?.id !== undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
</style>
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<b-icon :icon="'bullhorn'" :type="iconColor" />
|
||||
<div class="subject">
|
||||
<i18n :path="translation" tag="p">
|
||||
<router-link
|
||||
v-if="activity.object"
|
||||
slot="post"
|
||||
:to="{
|
||||
name: RouteName.POST,
|
||||
params: { slug: subjectParams.post_slug },
|
||||
}"
|
||||
>{{ subjectParams.post_title }}</router-link
|
||||
>
|
||||
<b v-else slot="post">{{ subjectParams.post_title }}</b>
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<small class="has-text-grey activity-date">{{
|
||||
activity.insertedAt | formatTimeString
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { ActivityPostSubject } from "@/types/enums";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
import ActivityMixin from "../../mixins/activity";
|
||||
import { mixins } from "vue-class-component";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PopoverActorCard,
|
||||
},
|
||||
})
|
||||
export default class PostActivityItem extends mixins(ActivityMixin) {
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
RouteName = RouteName;
|
||||
ActivityPostSubject = ActivityPostSubject;
|
||||
|
||||
get translation(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityPostSubject.POST_CREATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You created the post {post}.";
|
||||
}
|
||||
return "The post {post} was created by {profile}.";
|
||||
case ActivityPostSubject.POST_UPDATED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You updated the post {post}.";
|
||||
}
|
||||
return "The post {post} was updated by {profile}.";
|
||||
case ActivityPostSubject.POST_DELETED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You deleted the post {post}.";
|
||||
}
|
||||
return "The post {post} was deleted by {profile}.";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get iconColor(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityPostSubject.POST_CREATED:
|
||||
return "is-success";
|
||||
case ActivityPostSubject.POST_UPDATED:
|
||||
return "is-grey";
|
||||
case ActivityPostSubject.POST_DELETED:
|
||||
return "is-danger";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
</style>
|
@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<b-icon :icon="'link'" :type="iconColor" />
|
||||
<div class="subject">
|
||||
<i18n :path="translation" tag="p">
|
||||
<router-link v-if="activity.object" slot="resource" :to="path">{{
|
||||
subjectParams.resource_title
|
||||
}}</router-link>
|
||||
<b v-else slot="resource">{{ subjectParams.resource_title }}</b>
|
||||
<router-link v-if="activity.object" slot="new_path" :to="path">{{
|
||||
parentDirectory
|
||||
}}</router-link>
|
||||
<b v-else slot="new_path">{{ parentDirectory }}</b>
|
||||
<router-link
|
||||
v-if="activity.object && subjectParams.old_resource_title"
|
||||
slot="old_resource_title"
|
||||
:to="path"
|
||||
>{{ subjectParams.old_resource_title }}</router-link
|
||||
>
|
||||
<b
|
||||
v-else-if="subjectParams.old_resource_title"
|
||||
slot="old_resource_title"
|
||||
>{{ subjectParams.old_resource_title }}</b
|
||||
>
|
||||
|
||||
<popover-actor-card
|
||||
:actor="activity.author"
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<small class="has-text-grey activity-date">{{
|
||||
activity.insertedAt | formatTimeString
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { ActivityResourceSubject } from "@/types/enums";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
import PopoverActorCard from "../Account/PopoverActorCard.vue";
|
||||
import ActivityMixin from "../../mixins/activity";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { Location } from "vue-router";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PopoverActorCard,
|
||||
},
|
||||
})
|
||||
export default class ResourceActivityItem extends mixins(ActivityMixin) {
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
RouteName = RouteName;
|
||||
|
||||
get translation(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityResourceSubject.RESOURCE_CREATED:
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (this.activity?.object?.type === "folder") {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You created the folder {resource}.";
|
||||
}
|
||||
return "{profile} created the folder {resource}.";
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You created the resource {resource}.";
|
||||
}
|
||||
return "{profile} created the resource {resource}.";
|
||||
case ActivityResourceSubject.RESOURCE_MOVED:
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (this.activity?.object?.type === "folder") {
|
||||
if (this.parentDirectory === null) {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You moved the folder {resource} to the root folder.";
|
||||
}
|
||||
return "{profile} moved the folder {resource} to the root folder.";
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You moved the folder {resource} into {new_path}.";
|
||||
}
|
||||
return "{profile} moved the folder {resource} into {new_path}.";
|
||||
}
|
||||
if (this.parentDirectory === null) {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You moved the resource {resource} to the root folder.";
|
||||
}
|
||||
return "{profile} moved the resource {resource} to the root folder.";
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You moved the resource {resource} into {new_path}.";
|
||||
}
|
||||
return "{profile} moved the resource {resource} into {new_path}.";
|
||||
case ActivityResourceSubject.RESOURCE_UPDATED:
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (this.activity?.object?.type === "folder") {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You renamed the folder from {old_resource_title} to {resource}.";
|
||||
}
|
||||
return "{profile} renamed the folder from {old_resource_title} to {resource}.";
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You renamed the resource from {old_resource_title} to {resource}.";
|
||||
}
|
||||
return "{profile} renamed the resource from {old_resource_title} to {resource}.";
|
||||
case ActivityResourceSubject.RESOURCE_DELETED:
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (this.activity?.object?.type === "folder") {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You deleted the folder {resource}.";
|
||||
}
|
||||
return "{profile} deleted the folder {resource}.";
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You deleted the resource {resource}.";
|
||||
}
|
||||
return "{profile} deleted the resource {resource}.";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get iconColor(): string | undefined {
|
||||
switch (this.activity.subject) {
|
||||
case ActivityResourceSubject.RESOURCE_CREATED:
|
||||
return "is-success";
|
||||
case ActivityResourceSubject.RESOURCE_MOVED:
|
||||
case ActivityResourceSubject.RESOURCE_UPDATED:
|
||||
return "is-grey";
|
||||
case ActivityResourceSubject.RESOURCE_DELETED:
|
||||
return "is-danger";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
get path(): Location {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
let path = this.parentPath(this.activity?.object?.path);
|
||||
if (path === "") {
|
||||
return {
|
||||
name: RouteName.RESOURCE_FOLDER_ROOT,
|
||||
params: {
|
||||
preferredUsername: usernameWithDomain(this.activity.group),
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: RouteName.RESOURCE_FOLDER,
|
||||
params: {
|
||||
path,
|
||||
preferredUsername: usernameWithDomain(this.activity.group),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get parentDirectory(): string | undefined | null {
|
||||
if (this.subjectParams.resource_path) {
|
||||
const parentPath = this.parentPath(this.subjectParams.resource_path);
|
||||
const directory = parentPath.split("/");
|
||||
return directory.pop();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
parentPath(parent: string): string {
|
||||
let path = parent.split("/");
|
||||
path.pop();
|
||||
return path.join("/").replace(/^\//, "");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="activity-item">
|
||||
<span>
|
||||
<b-skeleton circle width="32px" height="32px"></b-skeleton>
|
||||
</span>
|
||||
<div class="subject">
|
||||
<div class="content">
|
||||
<p>
|
||||
<b-skeleton active></b-skeleton>
|
||||
<b-skeleton active class="datetime"></b-skeleton>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
@import "./activity.scss";
|
||||
div.activity-item {
|
||||
flex: 1;
|
||||
|
||||
.subject {
|
||||
flex: 1;
|
||||
max-width: 600px;
|
||||
|
||||
.content p > div:last-child {
|
||||
max-width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,21 @@
|
||||
.activity-item {
|
||||
display: flex;
|
||||
span.icon {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 2px solid;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.has-text-grey {
|
||||
border-color: $grey;
|
||||
}
|
||||
}
|
||||
|
||||
.subject {
|
||||
padding: 0.25rem 0 0 0.5rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="observer" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import "intersection-observer";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class Observer extends Vue {
|
||||
@Prop({ required: false, default: () => ({}) }) options!: Record<string, any>;
|
||||
|
||||
observer!: IntersectionObserver;
|
||||
mounted(): void {
|
||||
this.observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry && entry.isIntersecting) {
|
||||
this.$emit("intersect");
|
||||
}
|
||||
}, this.options);
|
||||
|
||||
this.observer.observe(this.$el);
|
||||
}
|
||||
|
||||
destroyed(): void {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,31 @@
|
||||
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
|
||||
import { IActivity } from "@/types/activity.model";
|
||||
import { IActor } from "@/types/actor";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
})
|
||||
export default class ActivityMixin extends Vue {
|
||||
@Prop({ required: true, type: Object }) activity!: IActivity;
|
||||
currentActor!: IActor;
|
||||
|
||||
get subjectParams(): Record<string, string> {
|
||||
return this.activity.subjectParams.reduce(
|
||||
(acc: Record<string, string>, { key, value }) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get isAuthorCurrentActor(): boolean {
|
||||
return (
|
||||
this.activity.author.id === this.currentActor.id &&
|
||||
this.currentActor.id !== undefined
|
||||
);
|
||||
}
|
||||
}
|