Skip to content

Commit

Permalink
fix(KtComment): fix/enhance/refact KtComments
Browse files Browse the repository at this point in the history
- fix: stop avatar from getting squished
- fix: prefix all class names with `kt-`
- fix: stop the inline edit from resizing
- feat: add translations to edit/delete options
- fix: options not skipping stacking context (by using tippy)
- refact: introduce common components for reply button
- refact: common component for inline edit
- refact: common component for options
- refact: migrate to ts where possible
- refact: delete dead classes and move style sheets to
..relevant components rather than _comments.scss
  • Loading branch information
carsoli committed May 25, 2022
1 parent a63dffc commit 2ed4bc8
Show file tree
Hide file tree
Showing 19 changed files with 539 additions and 485 deletions.
48 changes: 29 additions & 19 deletions packages/documentation/pages/usage/components/comment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,22 +146,6 @@ methods: {
/>
</div>

### Props

| Attribute | Description | Type | Accepted values | Default |
| :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------- | :-------------------------------- | :--------------------- |
| `createdTime` | The Time that appears in the comment | string | "20-12-2008" | - |
| `id` | the id to track the comment | number, string | "1" | - |
| `replies` | array of comment props to be nested under the coment | [CommentProps] | [{id: "1", message: "hello"}] | - |
| `userAvatar` | url to image thumbnail | string | "https://someimage.com/image.png" | - |
| `userId` | id of user who made the comment to reply too | number, string | "2" | - |
| `userName` | name of user to display | string | "Jhone Doe" | - |
| `message` | the actual comment | string | "Hello" | - |
| `dangerDefaultParserOverride` | A function that processes and escapes the comment message before it is passed to the div that render it, as the name implies you're responsible for escaping if you use this | (string) => string | Function | lodash escape function |
| `postEscapeParser` | A function that processes the message after is has been escaped use this instead of `dangerDefaultParserOverride` | (string) => string | Function | (_) => _ |
| `isDeletable` | whether this comment is deletable | boolean | true,false | false |
| `isEditable` | whether this comment is editable | boolean | true,false | false |

### Event

| Event Name | Component | Payload | Description |
Expand Down Expand Up @@ -251,9 +235,35 @@ export default {
methods: {
dangerouslyOverrideParser: (msg) => escape(msg),
postEscapeParser: (msg) => msg.replace(/\n/g, '</br>'),
handleEdit(payload) {
// eslint-disable-next-line
console.log(payload)
handleEdit({ id, message, parentId }) {
if (parentId === null) {
const commentIndex = this.comments.findIndex(
(comment) => comment.id === id,
)
return (this.comments = [
...this.comments.slice(0, commentIndex),
{ ...this.comments[commentIndex], message },
...this.comments.slice(commentIndex + 1),
])
}
const parentCommentIndex = this.comments.findIndex(
(comment) => comment.id === parentId,
)
const oldReplies = this.comments[parentCommentIndex].replies
const replyIndex = oldReplies.findIndex((reply) => reply.id === id)
const newReplies = [
...oldReplies.slice(0, replyIndex),
{ ...oldReplies[replyIndex], message },
...oldReplies.slice(replyIndex + 1),
]
this.comments = [
...this.comments.slice(0, parentCommentIndex),
{ ...this.comments[parentCommentIndex], replies: newReplies },
...this.comments.slice(parentCommentIndex + 1),
]
},
handleSubmit(payload) {
const _message = {
Expand Down
169 changes: 77 additions & 92 deletions packages/kotti-ui/source/kotti-comment/KtComment.vue
Original file line number Diff line number Diff line change
@@ -1,65 +1,39 @@
<template>
<div class="comment">
<div class="kt-comment">
<KtAvatar size="sm" :src="userAvatar" />
<div class="comment__wrapper">
<div class="comment__info">
<div class="kt-comment__content">
<div class="kt-comment__content__info">
<div class="info__name" v-text="userName" />
<div class="info__time" v-text="createdTime" />
</div>
<!-- eslint-disable vue/no-v-html -->
<div
v-if="!isInlineEdit"
class="comment__message"
v-html="postEscapeParser(dangerouslyOverrideParser(inlineMessage))"

<CommentInlineEdit
:id="id"
:dangerouslyOverrideParser="dangerouslyOverrideParser"
:isEditing="isEditing"
:message="message"
:postEscapeParser="postEscapeParser"
@edit="handleEdit($event)"
@update:isEditing="($event) => (isEditing = $event)"
/>
<!-- eslint-enable vue/no-v-html -->
<div v-else class="comment-inline-edit form-group">
<textarea
v-model="inlineMessageValue"
class="comment-inline-edit-input form-input"
></textarea>
<KtButtonGroup class="comment-inline-edit-buttons">
<KtButton icon="close" @click="cancelInlineEdit" />
<KtButton icon="check" @click="handleEditConfirm" />
</KtButtonGroup>
</div>
<div class="comment__action">
<div
class="action__reply"
@click="handleInlineReplyClick({ userName, userId })"
>
<i class="yoco" v-text="'comment'" /> {{ replyButton }}
</div>
<div v-if="actionOptions.length > 0" class="action__more">
<i class="yoco">dots</i>
<div class="action__options">
<a
v-for="option in actionOptions"
:key="option.type"
@click="option.onClick"
>
<li>{{ option.label }}</li>
</a>
</div>
</div>
</div>

<CommentActions
:options="actionOptions"
:userData="{ userId, userName }"
@replyClick="handleReplyClick"
/>

<div v-for="reply in replies" :key="reply.id">
<CommentReply
:id="reply.id"
:createdTime="reply.createdTime"
v-bind="reply"
:dangerouslyOverrideParser="dangerouslyOverrideParser"
:isDeletable="reply.isDeletable"
:isEditable="reply.isEditable"
:message="reply.message"
:postEscapeParser="postEscapeParser"
:userAvatar="reply.userAvatar"
:userId="reply.userId"
:userName="reply.userName"
@_inlineDeleteClick="(commentId) => handleDelete(commentId, true)"
@_inlineEditSubmit="$emit('edit', $event)"
@_inlineReplyClick="handleInlineReplyClick"
@click="handleReplyClick"
@delete="(commentId) => handleDelete(commentId, true)"
@edit="(editPayload) => handleEdit(editPayload, true)"
/>
</div>

<KtCommentInput
v-if="userBeingRepliedTo"
isInline
Expand All @@ -76,87 +50,80 @@
<script lang="ts">
import { computed, defineComponent, ref } from '@vue/composition-api'
import { KtAvatar } from '../kotti-avatar'
import { KtButton } from '../kotti-button'
import { KtButtonGroup } from '../kotti-button-group'
import { KottiButton } from '../kotti-button/types'
import { useTranslationNamespace } from '../kotti-i18n/hooks'
import { makeProps } from '../make-props'
import { Kotti } from '../types'
import CommentActions from './components/CommentActions.vue'
import CommentInlineEdit from './components/CommentInlineEdit.vue'
import CommentReply from './components/CommentReply.vue'
import KtCommentInput from './KtCommentInput.vue'
import { KottiComment } from './types'
type UserData = Pick<KottiComment.PropsInternal, 'userName' | 'userId'>
export default defineComponent<KottiComment.PropsInternal>({
name: 'KtComment',
components: {
KtAvatar,
KtButton,
KtButtonGroup,
CommentActions,
CommentReply,
CommentInlineEdit,
KtCommentInput,
},
props: makeProps(KottiComment.propsSchema),
setup(props, { emit }) {
const isInlineEdit = ref(false)
const inlineMessageValue = ref<string | null>(null)
const userBeingRepliedTo = ref<UserData | null>(null)
const isEditing = ref(false)
const userBeingRepliedTo = ref<Kotti.Comment.UserData | null>(null)
const translations = useTranslationNamespace('KtComment')
const handleDelete = (commentId: number | string, isInline?: boolean) => {
const handleDelete = (commentId: number | string, isReply = false) => {
const payload: KottiComment.Events.Delete = {
id: commentId,
parentId: isInline ? props.id : null,
parentId: isReply ? props.id : null,
}
emit('delete', payload)
}
const handleEdit = (
{ id, message }: KottiComment.Events.InternalEdit,
isReply = false,
) => {
const payload: KottiComment.Events.Edit = {
id,
message,
parentId: isReply ? props.id : null,
}
emit('edit', payload)
}
return {
actionOptions: computed<Array<KottiButton.Props>>(() => {
actionOptions: computed<Kotti.Popover.Props['options']>(() => {
const options = []
if (isInlineEdit.value) return options
if (isEditing.value) return options
if (props.isEditable)
options.push({
label: 'Edit',
label: translations.value.editButton,
onClick: () => {
inlineMessageValue.value = props.message
isInlineEdit.value = true
isEditing.value = true
},
type: KottiButton.Type.PRIMARY,
})
if (props.isDeletable)
options.push({
label: 'Delete',
label: translations.value.deleteButton,
onClick: () => handleDelete(props.id),
type: KottiButton.Type.DANGER,
})
return options
}),
cancelInlineEdit: () => {
inlineMessageValue.value = null
isInlineEdit.value = false
},
handleDelete,
handleEditConfirm: () => {
isInlineEdit.value = false
if (inlineMessageValue.value === null) return
const payload: KottiComment.Events.Edit = {
id: props.id,
message: inlineMessageValue.value,
}
emit('edit', payload)
},
handleInlineReplyClick: (replyUserData: UserData) => {
handleEdit,
handleReplyClick: (replyUserData: Kotti.Comment.UserData) => {
userBeingRepliedTo.value = replyUserData
},
handleInlineSubmit: (commentData: KottiComment.Events.Submit) => {
userBeingRepliedTo.value = null
emit('submit', commentData)
},
inlineMessage: computed(() => inlineMessageValue.value ?? props.message),
inlineMessageValue,
isInlineEdit,
isEditing,
Kotti,
placeholder: computed(() =>
userBeingRepliedTo.value === null
? null
Expand All @@ -165,16 +132,34 @@ export default defineComponent<KottiComment.PropsInternal>({
userBeingRepliedTo.value.userName,
].join(' '),
),
replyButton: computed(() => translations.value.replyButton),
userBeingRepliedTo,
}
},
})
</script>

<style lang="scss" scoped>
.action__reply {
.kt-comment {
display: flex;
align-items: center;
flex-flow: row;
+ .kt-comment {
padding-top: var(--unit-1);
border-top: 1px solid var(--ui-02);
}
&__content {
display: flex;
flex: 1;
flex-direction: column;
margin-left: var(--unit-2);
&__info {
display: flex;
width: 100%;
font-size: var(--font-size);
line-height: 1.2rem;
}
}
}
</style>
Loading

0 comments on commit 2ed4bc8

Please sign in to comment.