Improve group related UI

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-06-15 17:25:33 +02:00
parent 9639a066ff
commit 6cc233a6d3
22 changed files with 892 additions and 427 deletions

View File

@@ -49,7 +49,7 @@
<picture-upload
v-model="pictureFile"
:textFallback="$t('Headline picture')"
:defaultImage="post.picture"
:defaultImage="editablePost.picture"
/>
<b-field
@@ -61,21 +61,21 @@
size="is-large"
aria-required="true"
required
v-model="post.title"
v-model="editablePost.title"
/>
</b-field>
<tag-input v-model="post.tags" :data="tags" path="title" />
<tag-input v-model="editablePost.tags" :data="tags" path="title" />
<div class="field">
<label class="label">{{ $t("Post") }}</label>
<p v-if="errors.body" class="help is-danger">{{ errors.body }}</p>
<editor v-model="post.body" />
<editor v-model="editablePost.body" />
</div>
<subtitle>{{ $t("Who can view this post") }}</subtitle>
<div class="field">
<b-radio
v-model="post.visibility"
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.PUBLIC"
>{{ $t("Visible everywhere on the web") }}</b-radio
@@ -83,7 +83,7 @@
</div>
<div class="field">
<b-radio
v-model="post.visibility"
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.UNLISTED"
>{{ $t("Only accessible through link") }}</b-radio
@@ -91,7 +91,7 @@
</div>
<div class="field">
<b-radio
v-model="post.visibility"
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.PRIVATE"
>{{ $t("Only accessible to members of the group") }}</b-radio
@@ -166,7 +166,7 @@ import {
import { IPost } from "../../types/post.model";
import Editor from "../../components/Editor.vue";
import { IActor, IGroup, usernameWithDomain } from "../../types/actor";
import { IActor, usernameWithDomain } from "../../types/actor";
import TagInput from "../../components/Event/TagInput.vue";
import RouteName from "../../router/name";
import Subtitle from "../../components/Utils/Subtitle.vue";
@@ -249,8 +249,6 @@ export default class EditPost extends mixins(GroupMixin) {
tags: [],
};
group!: IGroup;
PostVisibility = PostVisibility;
pictureFile: File | null = null;
@@ -259,6 +257,8 @@ export default class EditPost extends mixins(GroupMixin) {
RouteName = RouteName;
editablePost!: IPost;
usernameWithDomain = usernameWithDomain;
async mounted(): Promise<void> {
@@ -270,6 +270,7 @@ export default class EditPost extends mixins(GroupMixin) {
if (oldPost.picture !== newPost.picture) {
this.pictureFile = await buildFileFromIMedia(this.post.picture);
}
this.editablePost = { ...this.post };
}
// eslint-disable-next-line consistent-return
@@ -280,11 +281,11 @@ export default class EditPost extends mixins(GroupMixin) {
const { data } = await this.$apollo.mutate({
mutation: UPDATE_POST,
variables: {
id: this.post.id,
title: this.post.title,
body: this.post.body,
tags: (this.post.tags || []).map(({ title }) => title),
visibility: this.post.visibility,
id: this.editablePost.id,
title: this.editablePost.title,
body: this.editablePost.body,
tags: (this.editablePost.tags || []).map(({ title }) => title),
visibility: this.editablePost.visibility,
draft,
...(await this.buildPicture()),
},
@@ -300,9 +301,9 @@ export default class EditPost extends mixins(GroupMixin) {
const { data } = await this.$apollo.mutate({
mutation: CREATE_POST,
variables: {
...this.post,
...this.editablePost,
...(await this.buildPicture()),
tags: (this.post.tags || []).map(({ title }) => title),
tags: (this.editablePost.tags || []).map(({ title }) => title),
attributedToId: this.actualGroup.id,
draft,
},
@@ -362,16 +363,16 @@ export default class EditPost extends mixins(GroupMixin) {
obj = { ...obj, ...pictureObj };
}
try {
if (this.post.picture && this.pictureFile) {
if (this.editablePost.picture && this.pictureFile) {
const oldPictureFile = (await buildFileFromIMedia(
this.post.picture
this.editablePost.picture
)) as File;
const oldPictureFileContent = await readFileAsync(oldPictureFile);
const newPictureFileContent = await readFileAsync(
this.pictureFile as File
);
if (oldPictureFileContent === newPictureFileContent) {
obj.picture = { mediaId: this.post.picture.id };
obj.picture = { mediaId: this.editablePost.picture.id };
}
}
} catch (e) {

View File

@@ -1,61 +1,78 @@
<template>
<div>
<article class="container" v-if="post">
<section class="heading-section">
<h1 class="title">{{ post.title }}</h1>
<i18n tag="span" path="By {author}" class="authors">
<router-link
slot="author"
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(post.attributedTo),
},
}"
>{{ post.attributedTo.name }}</router-link
>
</i18n>
<p class="published has-text-grey-dark" v-if="!post.draft">
{{ post.publishAt | formatDateTimeString }}
</p>
<small
v-if="post.visibility === PostVisibility.PRIVATE"
class="has-text-grey-dark"
>
<b-icon icon="lock" size="is-small" />
{{
$t("Accessible only to members", { group: post.attributedTo.name })
}}
</small>
<p class="buttons" v-if="isCurrentActorMember">
<b-tag type="is-warning" size="is-medium" v-if="post.draft">{{
$t("Draft")
}}</b-tag>
<router-link
v-if="
currentActor.id === post.author.id ||
isCurrentActorAGroupModerator
"
:to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }"
tag="button"
class="button is-text"
>{{ $t("Edit") }}</router-link
>
</p>
<img v-if="post.picture" :src="post.picture.url" alt="" />
</section>
<section v-html="post.body" class="content" />
<section class="tags">
<router-link
v-for="tag in post.tags"
:key="tag.title"
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
>
<tag>{{ tag.title }}</tag>
</router-link>
</section>
</article>
</div>
<article class="container" v-if="post">
<header>
<div class="banner-container">
<lazy-image
v-if="post.picture"
:src="post.picture.url"
:width="post.picture.metadata.width"
:height="post.picture.metadata.height"
:blurhash="post.picture.metadata.blurhash"
/>
</div>
<div class="heading-section">
<div class="heading-wrapper">
<div class="title-metadata">
<h1 class="title">{{ post.title }}</h1>
<p class="metadata">
<router-link
slot="author"
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(post.attributedTo),
},
}"
>
<actor-inline :actor="post.attributedTo" />
</router-link>
<span class="published has-text-grey-dark" v-if="!post.draft">
<b-icon icon="clock" size="is-small" />
{{ post.publishAt | formatDateTimeString }}
</span>
<span
v-if="post.visibility === PostVisibility.PRIVATE"
class="has-text-grey-dark"
>
<b-icon icon="lock" size="is-small" />
{{
$t("Accessible only to members", {
group: post.attributedTo.name,
})
}}
</span>
</p>
</div>
<p class="buttons" v-if="isCurrentActorMember">
<b-tag type="is-warning" size="is-medium" v-if="post.draft">{{
$t("Draft")
}}</b-tag>
<router-link
v-if="
currentActor.id === post.author.id ||
isCurrentActorAGroupModerator
"
:to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }"
tag="button"
class="button is-text"
>{{ $t("Edit") }}</router-link
>
</p>
</div>
</div>
</header>
<section v-html="post.body" class="content" />
<section class="tags">
<router-link
v-for="tag in post.tags"
:key="tag.title"
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
>
<tag>{{ tag.title }}</tag>
</router-link>
</section>
</article>
</template>
<script lang="ts">
@@ -66,11 +83,12 @@ import { PostVisibility } from "@/types/enums";
import { IMember } from "@/types/actor/member.model";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { FETCH_POST } from "../../graphql/post";
import { IPost } from "../../types/post.model";
import { usernameWithDomain } from "../../types/actor";
import RouteName from "../../router/name";
import Tag from "../../components/Tag.vue";
import LazyImage from "../../components/Image/LazyImage.vue";
import ActorInline from "../../components/Account/ActorInline.vue";
@Component({
apollo: {
@@ -106,6 +124,8 @@ import Tag from "../../components/Tag.vue";
},
components: {
Tag,
LazyImage,
ActorInline,
},
metaInfo() {
return {
@@ -148,78 +168,93 @@ export default class Post extends mixins(GroupMixin) {
</script>
<style lang="scss" scoped>
article {
section.heading-section {
text-align: center;
position: relative;
background: $white !important;
header {
display: flex;
flex-direction: column;
margin: auto -3rem 2rem;
h1.title {
margin: 0 auto;
padding-top: 3rem;
font-size: 3rem;
font-weight: 700;
}
.authors {
margin-top: 2rem;
display: inline-block;
}
.published {
margin-top: 1rem;
}
&::after {
height: 0.2rem;
content: " ";
display: block;
width: 100%;
background-color: $purple-1;
margin-top: 1rem;
}
.buttons {
.banner-container {
display: flex;
justify-content: center;
}
& > * {
z-index: 10;
height: 30vh;
}
& > img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0.3;
object-fit: cover;
object-position: 50% 50%;
z-index: 0;
}
}
.heading-section {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 2rem;
section.content {
font-size: 1.1rem;
}
.heading-wrapper {
padding: 15px 10px;
display: flex;
flex-wrap: wrap;
justify-content: center;
section.tags {
padding-bottom: 5rem;
.title-metadata {
min-width: 300px;
flex: 20;
a {
text-decoration: none;
}
span {
&.tag {
margin: 0 2px;
p.metadata {
margin-top: 16px;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
*:not(:first-child) {
padding-left: 5px;
}
}
}
p.buttons {
flex: 1;
}
}
h1.title {
margin: 0;
font-weight: 500;
font-size: 38px;
font-family: "Roboto", "Helvetica", "Arial", serif;
}
.authors {
display: inline-block;
}
&::after {
height: 0.2rem;
content: " ";
display: block;
background-color: $purple-1;
}
.buttons {
justify-content: center;
}
}
}
& > section {
margin: 0 2rem;
&.content {
font-size: 1.1rem;
}
&.tags {
padding-bottom: 5rem;
a {
text-decoration: none;
}
span {
&.tag {
margin: 0 2px;
}
}
}
}
background: $white;
max-width: 700px;
margin: 0 auto;
padding: 0 3rem;
}
</style>