Various UI fixes, add placeholder to the text editor

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2022-10-31 11:43:18 +01:00
parent 9ce618a267
commit 695d773d50
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
17 changed files with 111 additions and 243 deletions

View File

@ -41,6 +41,7 @@
"@tiptap/extension-mention": "^2.0.0-beta.42",
"@tiptap/extension-ordered-list": "^2.0.0-beta.24",
"@tiptap/extension-paragraph": "^2.0.0-beta.22",
"@tiptap/extension-placeholder": "^2.0.0-beta.199",
"@tiptap/extension-strike": "^2.0.0-beta.26",
"@tiptap/extension-text": "^2.0.0-beta.15",
"@tiptap/extension-underline": "^2.0.0-beta.7",

View File

@ -26,6 +26,7 @@
v-model="newComment.text"
:aria-label="t('Comment body')"
@submit="createCommentForEvent(newComment)"
:placeholder="t('Write a new comment')"
/>
<p class="" v-if="emptyCommentError">
{{ t("Comment text can't be empty") }}

View File

@ -155,6 +155,7 @@
:aria-label="t('Comment body')"
class="flex-1"
@submit="replyToComment"
:placeholder="t('Write a new reply')"
/>
<o-button
:disabled="newComment.text.trim().length === 0"
@ -201,7 +202,7 @@
</li>
</template>
<script lang="ts" setup>
import EditorComponent from "@/components/TextEditor.vue";
import type EditorComponent from "@/components/TextEditor.vue";
import { formatDistanceToNow } from "date-fns";
import { CommentModeration } from "@/types/enums";
import { CommentModel, IComment } from "../../types/comment.model";

View File

@ -22,7 +22,7 @@
<small>@{{ usernameWithDomain(comment.actor) }}</small>
</div>
<span v-else class="name comment-link has-text-grey">
{{ $t("[deleted]") }}
{{ t("[deleted]") }}
</span>
<span
class="icons"
@ -43,7 +43,7 @@
aria-role="menuitem"
>
<o-icon icon="pencil"></o-icon>
{{ $t("Edit") }}
{{ t("Edit") }}
</o-dropdown-item>
<o-dropdown-item
v-if="comment.actor?.id === currentActor?.id"
@ -51,11 +51,11 @@
aria-role="menuitem"
>
<o-icon icon="delete"></o-icon>
{{ $t("Delete") }}
{{ t("Delete") }}
</o-dropdown-item>
<!-- <o-dropdown-item aria-role="listitem" @click="isReportModalActive = true">
<o-icon icon="flag" />
{{ $t("Report") }}
{{ t("Report") }}
</o-dropdown-item> -->
</o-dropdown>
</span>
@ -67,7 +67,7 @@
{{
formatDistanceToNow(new Date(comment.updatedAt?.toString()), {
locale: dateFnsLocale,
}) || $t("Right now")
}) || t("Right now")
}}</span
>
</div>
@ -92,7 +92,7 @@
:title="formatDateTimeString(comment.updatedAt.toString())"
>
{{
$t("Edited {ago}", {
t("Edited {ago}", {
ago: formatDistanceToNow(new Date(comment.updatedAt), {
locale: dateFnsLocale,
}),
@ -101,23 +101,24 @@
</p>
</div>
<div class="comment-deleted" v-else-if="!editMode">
{{ $t("[This comment has been deleted by it's author]") }}
{{ t("[This comment has been deleted by it's author]") }}
</div>
<form v-else class="edition" @submit.prevent="updateComment">
<Editor
v-model="updatedComment"
:aria-label="$t('Comment body')"
:aria-label="t('Comment body')"
:current-actor="currentActor"
:placeholder="t('Write a new message')"
/>
<div class="flex gap-2 mt-2">
<o-button
native-type="submit"
:disabled="['<p></p>', '', comment.text].includes(updatedComment)"
variant="primary"
>{{ $t("Update") }}</o-button
>{{ t("Update") }}</o-button
>
<o-button native-type="button" @click="toggleEditMode">{{
$t("Cancel")
t("Cancel")
}}</o-button>
</div>
</form>
@ -132,6 +133,7 @@ import { computed, defineAsyncComponent, inject, ref } from "vue";
import { formatDateTimeString } from "@/filters/datetime";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import type { Locale } from "date-fns";
import { useI18n } from "vue-i18n";
const Editor = defineAsyncComponent(
() => import("@/components/TextEditor.vue")
@ -144,6 +146,8 @@ const props = defineProps<{
const emit = defineEmits(["update:modelValue", "deleteComment"]);
const { t } = useI18n({useScope: 'global'});
const comment = computed(() => props.modelValue);
const editMode = ref(false);

View File

@ -31,7 +31,7 @@
:id="`availableActor-${availableActor?.id}`"
/>
<label
class="flex flex-wrap p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
class="flex flex-wrap gap-2 p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
:for="`availableActor-${availableActor?.id}`"
>
<figure class="h-12 w-12" v-if="availableActor?.avatar">

View File

@ -238,6 +238,7 @@ import FormatListNumbered from "vue-material-design-icons/FormatListNumbered.vue
import FormatQuoteClose from "vue-material-design-icons/FormatQuoteClose.vue";
import Undo from "vue-material-design-icons/Undo.vue";
import Redo from "vue-material-design-icons/Redo.vue";
import Placeholder from '@tiptap/extension-placeholder'
const props = withDefaults(
defineProps<{
@ -246,6 +247,7 @@ const props = withDefaults(
maxSize?: number;
ariaLabel?: string;
currentActor: IPerson;
placeholder?: string;
}>(),
{
mode: "description",
@ -291,6 +293,8 @@ const transformPastedHTML = (html: string): string => {
return html;
};
const { t } = useI18n({ useScope: "global" });
const editor = useEditor({
editorProps: {
attributes: {
@ -327,6 +331,9 @@ const editor = useEditor({
RichEditorKeyboardSubmit.configure({
submit: () => emit("submit"),
}),
Placeholder.configure({
placeholder: props.placeholder ?? t('Write something')
})
],
injectCSS: false,
content: props.modelValue,
@ -345,7 +352,6 @@ watch(value, (val: string) => {
});
const dialog = inject<Dialog>("dialog");
const { t } = useI18n({ useScope: "global" });
/**
* Show a popup to get the link from the URL
@ -594,4 +600,12 @@ onBeforeUnmount(() => {
.visually-hidden {
display: none;
}
.ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
</style>

View File

@ -1417,5 +1417,13 @@
"Theme": "Theme",
"Adapt to system theme": "Adapt to system theme",
"Light": "Light",
"Dark": "Dark"
"Dark": "Dark",
"Write a new comment": "Write a new comment",
"Write a new reply": "Write a new reply",
"Write a new message": "Write a new message",
"Write something": "Write something",
"Message body": "Message body",
"Describe your event": "Describe your event",
"A few lines about your group": "A few lines about your group",
"Write your post": "Write your post"
}

View File

@ -1415,5 +1415,13 @@
"Theme": "Thème",
"Adapt to system theme": "Sadapter au thème du système",
"Light": "Clair",
"Dark": "Sombre"
"Dark": "Sombre",
"Write a new comment": "Écrivez un nouveau commentaire",
"Write a new reply": "Écrivez une nouvelle réponse",
"Write a new message": "Écrivez un nouveau message",
"Write something": "Écrivez quelque chose",
"Message body": "Corps du message",
"Describe your event": "Décrivez votre événement",
"A few lines about your group": "Quelques lignes à propos de votre groupe",
"Write your post": "Écrivez votre billet"
}

View File

@ -28,7 +28,7 @@
:id="`availableActor-${identity?.id}`"
/>
<label
class="flex flex-wrap p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
class="flex flex-wrap gap-2 p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
:for="`availableActor-${identity?.id}`"
>
<figure class="h-12 w-12" v-if="identity?.avatar">

View File

@ -42,9 +42,9 @@
class="cursor-pointer"
@click="activateModal"
>
<figure class="" v-if="currentIdentity.avatar">
<figure class="h-12 w-12" v-if="currentIdentity.avatar">
<img
class="rounded"
class="rounded-full object-cover h-full"
:src="currentIdentity.avatar.url"
alt=""
width="48"

View File

@ -44,9 +44,10 @@
<o-field :label="t('Text')">
<Editor
v-model="discussion.text"
:aria-label="t('Comment body')"
:aria-label="t('Message body')"
v-if="currentActor"
:current-actor="currentActor"
:placeholder="t('Write a new message')"
/>
</o-field>

View File

@ -5,7 +5,7 @@
:links="[
{
name: RouteName.MY_GROUPS,
text: $t('My groups'),
text: t('My groups'),
},
{
name: RouteName.GROUP,
@ -15,7 +15,7 @@
{
name: RouteName.DISCUSSION_LIST,
params: { preferredUsername: usernameWithDomain(group) },
text: $t('Discussions'),
text: t('Discussions'),
},
{
name: RouteName.DISCUSSION,
@ -35,7 +35,7 @@
<o-button
icon-right="pencil"
size="small"
:title="$t('Update discussion title')"
:title="t('Update discussion title')"
v-if="
discussion.creator &&
!editTitleMode &&
@ -60,7 +60,7 @@
@submit.prevent="updateDiscussion"
class="w-full"
>
<o-field :label="$t('Title')" label-for="discussion-title">
<o-field :label="t('Title')" label-for="discussion-title">
<o-input
:value="discussion.title"
v-model="newTitle"
@ -72,7 +72,7 @@
variant="primary"
native-type="submit"
icon-right="check"
:title="$t('Update discussion title')"
:title="t('Update discussion title')"
/>
<o-button
@click="
@ -82,14 +82,14 @@
}
"
icon-right="close"
:title="$t('Cancel discussion title edition')"
:title="t('Cancel discussion title edition')"
/>
<o-button
@click="openDeleteDiscussionConfirmation"
variant="danger"
native-type="button"
icon-left="delete"
>{{ $t("Delete conversation") }}</o-button
>{{ t("Delete conversation") }}</o-button
>
</div>
</form>
@ -114,15 +114,16 @@
<o-button
v-if="discussion.comments.elements.length < discussion.comments.total"
@click="loadMoreComments"
>{{ $t("Fetch more") }}</o-button
>{{ t("Fetch more") }}</o-button
>
<form @submit.prevent="reply" v-if="!error">
<o-field :label="$t('Text')">
<o-field :label="t('Text')">
<Editor
v-model="newComment"
:aria-label="$t('Comment body')"
:aria-label="t('Message body')"
v-if="currentActor"
:currentActor="currentActor"
:placeholder="t('Write a new message')"
/>
</o-field>
<o-button
@ -130,7 +131,7 @@
native-type="submit"
:disabled="['<p></p>', ''].includes(newComment)"
variant="primary"
>{{ $t("Reply") }}</o-button
>{{ t("Reply") }}</o-button
>
</form>
</section>

View File

@ -129,6 +129,7 @@
:current-actor="(currentActor as IPerson)"
v-model="event.description"
:aria-label="t('Event description body')"
:placeholder="t('Describe your event')"
/>
</div>

View File

@ -10,7 +10,7 @@
<section class="intro" dir="auto">
<div class="flex flex-wrap gap-2">
<div class="flex-1 min-w-fit">
<div class="flex-1">
<h1
class="text-4xl font-bold m-0"
dir="auto"
@ -108,10 +108,10 @@
</div>
<div
class="event-description-wrapper rounded-lg dark:border-violet-title flex flex-wrap flex-col md:flex-row-reverse"
class="rounded-lg dark:border-violet-title flex flex-wrap flex-col md:flex-row-reverse gap-4"
>
<aside
class="event-metadata rounded bg-white dark:bg-gray-600 shadow-md h-min"
class="rounded bg-white dark:bg-gray-600 shadow-md h-min"
>
<div class="sticky p-4">
<event-metadata-sidebar
@ -122,9 +122,9 @@
/>
</div>
</aside>
<div class="event-description-comments">
<section class="event-description">
<h2 class="text-xl">{{ t("About this event") }}</h2>
<div class="flex-1">
<section class="event-description bg-white dark:bg-zinc-700 px-3 pt-1 pb-3 rounded mb-4">
<h2 class="text-2xl">{{ t("About this event") }}</h2>
<p v-if="!event?.description">
{{ t("The event organizer didn't add any description.") }}
</p>
@ -138,7 +138,7 @@
/>
</div>
</section>
<section class="integration-wrappers">
<section class="my-4">
<component
v-for="(metadata, integration) in integrations"
:is="integration"
@ -146,7 +146,7 @@
:metadata="metadata"
/>
</section>
<section class="comments" ref="commentsObserver">
<section class="bg-white dark:bg-zinc-700 px-3 pt-1 pb-3 rounded my-4" ref="commentsObserver">
<a href="#comments">
<h2 class="text-xl" id="comments">{{ t("Comments") }}</h2>
</a>
@ -155,8 +155,8 @@
</div>
</div>
<section class="" v-if="(event?.relatedEvents ?? []).length > 0">
<h2 class="">
<section class="bg-white dark:bg-zinc-700 px-3 pt-1 pb-3 rounded my-4" v-if="(event?.relatedEvents ?? []).length > 0">
<h2 class="text-2xl mb-2">
{{ t("These events may interest you") }}
</h2>
<multi-card :events="event?.relatedEvents ?? []" />
@ -492,192 +492,9 @@ useHead({
meta: [{ name: "description", content: eventDescription.value }],
});
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
.section {
padding: 1rem 2rem 4rem;
}
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
div.sidebar {
display: flex;
flex-wrap: wrap;
flex-direction: column;
position: relative;
&::before {
content: "";
background: #b3b3b2;
position: absolute;
bottom: 30px;
top: 30px;
left: 0;
height: calc(100% - 60px);
width: 1px;
}
div.organizer {
display: inline-flex;
padding-top: 10px;
a {
color: #4a4a4a;
span {
line-height: 2.7rem;
@include padding-right(6px);
}
}
}
}
.intro {
// background: white;
.is-3-tablet {
width: initial;
}
p.tags {
a {
text-decoration: none;
}
span {
&.tag {
margin: 0 2px;
}
}
}
}
.event-description-wrapper {
padding: 0;
aside.event-metadata {
min-width: 20rem;
flex: 1;
.sticky {
// position: sticky;
// background: white;
top: 50px;
padding: 1rem;
}
}
div.event-description-comments {
min-width: 20rem;
padding: 1rem;
flex: 2;
// background: white;
}
.description-content {
:deep(h1) {
font-size: 2rem;
}
:deep(h2) {
font-size: 1.5rem;
}
:deep(h3) {
font-size: 1.25rem;
}
:deep(ul) {
list-style-type: disc;
}
:deep(li) {
margin: 10px auto 10px 2rem;
}
:deep(blockquote) {
border-left: 0.2em solid #333;
display: block;
@include padding-left(1rem);
}
:deep(p) {
margin: 10px auto;
a {
display: inline-block;
padding: 0.3rem;
color: #111;
&:empty {
display: none;
}
}
}
}
}
.comments {
padding-top: 3rem;
a h3#comments {
margin-bottom: 10px;
}
}
.more-events {
// background: white;
padding: 1rem 1rem 4rem;
& > .title {
font-size: 1.5rem;
}
}
// .dropdown .dropdown-trigger span {
// cursor: pointer;
// }
// a.dropdown-item,
// .dropdown .dropdown-menu .has-link a,
// button.dropdown-item {
// white-space: nowrap;
// width: 100%;
// @include padding-right(1rem);
// text-align: right;
// }
a.participations-link {
text-decoration: none;
}
.no-border {
border: 0;
cursor: auto;
}
.intro-wrapper {
.date-calendar-icon-wrapper {
margin-top: 16px;
height: 0;
display: flex;
align-items: flex-end;
align-self: flex-start;
margin-bottom: 7px;
@include margin-left(0);
}
}
.title {
margin: 0;
font-size: 2rem;
.event-description a {
@apply inline-block p-1 bg-mbz-yellow-alt-200 text-black;
}
</style>

View File

@ -37,6 +37,7 @@
:aria-label="t('Group description body')"
v-if="currentActor"
:currentActor="currentActor"
:placeholder="t('A few lines about your group')"
/></o-field>
<o-field :label="t('Avatar')">
<picture-upload

View File

@ -4,20 +4,20 @@
<div class="container mx-auto">
<breadcrumbs-nav v-if="actualGroup" :links="breadcrumbLinks" />
<h1 v-if="isUpdate === true">
{{ $t("Edit post") }}
{{ t("Edit post") }}
</h1>
<h1 v-else>
{{ $t("Add a new post") }}
{{ t("Add a new post") }}
</h1>
<h2>{{ $t("General information") }}</h2>
<h2>{{ t("General information") }}</h2>
<picture-upload
v-model="pictureFile"
:textFallback="$t('Headline picture')"
:textFallback="t('Headline picture')"
:defaultImage="editablePost.picture"
/>
<o-field
:label="$t('Title')"
:label="t('Title')"
label-for="post-title"
:type="errors.title ? 'is-danger' : null"
:message="errors.title"
@ -40,15 +40,16 @@
class="w-full"
v-if="currentActor"
v-model="editablePost.body"
:aria-label="$t('Post body')"
:aria-label="t('Post body')"
:current-actor="currentActor"
:placeholder="t('Write your post')"
/>
</o-field>
<h2 class="mt-2">{{ $t("Who can view this post") }}</h2>
<h2 class="mt-2">{{ t("Who can view this post") }}</h2>
<fieldset>
<legend>
{{
$t(
t(
"When the post is private, you'll need to share the link around."
)
}}
@ -58,7 +59,7 @@
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.PUBLIC"
>{{ $t("Visible everywhere on the web") }}</o-radio
>{{ t("Visible everywhere on the web") }}</o-radio
>
</div>
<div class="field">
@ -66,7 +67,7 @@
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.UNLISTED"
>{{ $t("Only accessible through link") }}</o-radio
>{{ t("Only accessible through link") }}</o-radio
>
</div>
<div class="field">
@ -74,7 +75,7 @@
v-model="editablePost.visibility"
name="postVisibility"
:native-value="PostVisibility.PRIVATE"
>{{ $t("Only accessible to members of the group") }}</o-radio
>{{ t("Only accessible to members of the group") }}</o-radio
>
</div>
</fieldset>
@ -84,14 +85,14 @@
<div class="navbar-menu flex flex-wrap py-2">
<div class="flex flex-wrap justify-end ml-auto gap-1">
<o-button variant="text" @click="$router.go(-1)">{{
$t("Cancel")
t("Cancel")
}}</o-button>
<o-button
v-if="isUpdate"
variant="danger"
outlined
@click="openDeletePostModal"
>{{ $t("Delete post") }}</o-button
>{{ t("Delete post") }}</o-button
>
<!-- If an post has been published we can't make it draft anymore -->
<o-button
@ -99,14 +100,14 @@
v-if="post?.draft === true"
outlined
@click="publish(true)"
>{{ $t("Save draft") }}</o-button
>{{ t("Save draft") }}</o-button
>
<o-button variant="primary" native-type="submit">
<span v-if="isUpdate === false || post?.draft === true">{{
$t("Publish")
t("Publish")
}}</span>
<span v-else>{{ $t("Update post") }}</span>
<span v-else>{{ t("Update post") }}</span>
</o-button>
</div>
</div>
@ -121,7 +122,7 @@
></o-loading>
<div class="container mx-auto" v-else>
<o-notification variant="danger">
{{ $t("Only group moderators can create, edit and delete posts.") }}
{{ t("Only group moderators can create, edit and delete posts.") }}
</o-notification>
</div>
</div>

View File

@ -1585,6 +1585,15 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.199.tgz#34213e6594a1183a77bb33ced49502bafb0a3d1c"
integrity sha512-+BoMCaxlsHqw065zTUNd+ywkvFJzNKbTY461/AlKX2dgHeaO8doXHDQK+9icOpibQvrKaMhOJmuBTgGlJlUUgw==
"@tiptap/extension-placeholder@^2.0.0-beta.199":
version "2.0.0-beta.199"
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.0.0-beta.199.tgz#0208c42f8b92a88e66b726353d07b652f09fd823"
integrity sha512-Tdq0r9XQ6hcu4ASvw2Xko6h8uS/xONmMmOFiTkK/54REB3RRQpkdCtXrhFn/T4DunJVBf6FUOLTjYN3SONhuew==
dependencies:
prosemirror-model "^1.18.1"
prosemirror-state "^1.4.1"
prosemirror-view "^1.28.2"
"@tiptap/extension-strike@^2.0.0-beta.26":
version "2.0.0-beta.199"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.199.tgz#5fc6e067728009d92027e58a042f18449f2fa264"