Files
huly-platform/plugins/chunter-resources/src/components/chat-message/ChatMessageInput.svelte
T
2024-07-09 00:04:05 +07:00

211 lines
6.4 KiB
Svelte

<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import activity, { ActivityMessage } from '@hcengineering/activity'
import { Analytics } from '@hcengineering/analytics'
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
import chunter, { ChatMessage, ThreadMessage } from '@hcengineering/chunter'
import { PersonAccount } from '@hcengineering/contact'
import { Class, Doc, generateId, getCurrentAccount, Ref, type CommitResult } from '@hcengineering/core'
import { createQuery, DraftController, draftsStore, getClient, isSpace } from '@hcengineering/presentation'
import { EmptyMarkup } from '@hcengineering/text-editor'
import { createEventDispatcher } from 'svelte'
export let object: Doc
export let chatMessage: ChatMessage | undefined = undefined
export let shouldSaveDraft: boolean = true
export let focusIndex: number = -1
export let boundary: HTMLElement | undefined = undefined
export let loading = false
export let collection: string = 'comments'
export let autofocus = false
type MessageDraft = Pick<ChatMessage, '_id' | 'message' | 'attachments'>
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
const _class: Ref<Class<ChatMessage>> = hierarchy.isDerived(object._class, activity.class.ActivityMessage)
? chunter.class.ThreadMessage
: chunter.class.ChatMessage
const createdMessageQuery = createQuery()
const account = getCurrentAccount() as PersonAccount
const draftKey = `${object._id}_${_class}`
const draftController = new DraftController<MessageDraft>(draftKey)
const currentDraft = shouldSaveDraft ? $draftsStore[draftKey] : undefined
const emptyMessage: Pick<MessageDraft, 'message' | 'attachments'> = {
message: EmptyMarkup,
attachments: 0
}
let inputRef: AttachmentRefInput
let currentMessage: MessageDraft = chatMessage ?? currentDraft ?? getDefault()
let _id = currentMessage._id
let inputContent = currentMessage.message
$: if (currentDraft != null) {
createdMessageQuery.query(_class, { _id }, (result: ChatMessage[]) => {
if (result.length > 0 && _id !== chatMessage?._id) {
// Ouch we have got comment with same id created already.
clear()
}
})
} else {
createdMessageQuery.unsubscribe()
}
function clear (): void {
currentMessage = getDefault()
_id = currentMessage._id
inputRef.removeDraft(false)
}
function objectChange (draft: MessageDraft, empty: Partial<MessageDraft>): void {
if (shouldSaveDraft) {
draftController.save(draft, empty)
}
}
$: objectChange(currentMessage, emptyMessage)
function getDefault (): MessageDraft {
return {
_id: generateId(),
...emptyMessage
}
}
function onUpdate (event: CustomEvent): void {
if (!shouldSaveDraft) {
return
}
const { message, attachments } = event.detail
currentMessage.message = message
currentMessage.attachments = attachments
}
async function handleCreate (event: CustomEvent, _id: Ref<ChatMessage>): Promise<void> {
try {
const res = await createMessage(event, _id, `chunter.create.${_class} ${object._class}`)
console.log(`create.${_class} measure`, res.serverTime, res.time)
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
}
async function handleEdit (event: CustomEvent): Promise<void> {
try {
await editMessage(event)
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
}
async function onMessage (event: CustomEvent): Promise<void> {
draftController.remove()
inputRef.removeDraft(false)
if (chatMessage !== undefined) {
loading = true
await handleEdit(event)
} else {
void handleCreate(event, _id)
}
// Remove draft from Local Storage
clear()
dispatch('submit', false)
loading = false
}
async function createMessage (event: CustomEvent, _id: Ref<ChatMessage>, msg: string): Promise<CommitResult> {
const { message, attachments } = event.detail
const operations = client.apply(_id, msg)
if (_class === chunter.class.ThreadMessage) {
const parentMessage = object as ActivityMessage
await operations.addCollection<ActivityMessage, ThreadMessage>(
chunter.class.ThreadMessage,
parentMessage.space,
parentMessage._id,
parentMessage._class,
'replies',
{
message,
attachments,
objectClass: parentMessage.attachedToClass,
objectId: parentMessage.attachedTo
},
_id as Ref<ThreadMessage>
)
await operations.update(parentMessage, { lastReply: Date.now() })
const hasPerson = !!parentMessage.repliedPersons?.includes(account.person)
if (!hasPerson) {
await operations.update(parentMessage, { $push: { repliedPersons: account.person } })
}
} else {
await operations.addCollection<Doc, ChatMessage>(
_class,
isSpace(object) ? object._id : object.space,
object._id,
object._class,
collection,
{ message, attachments },
_id
)
}
return await operations.commit()
}
async function editMessage (event: CustomEvent): Promise<void> {
if (chatMessage === undefined) {
return
}
const { message, attachments } = event.detail
await client.update(chatMessage, { message, attachments, editedOn: Date.now() })
}
export function submit (): void {
inputRef.submit()
}
</script>
<AttachmentRefInput
{focusIndex}
bind:this={inputRef}
bind:content={inputContent}
{_class}
space={isSpace(object) ? object._id : object.space}
skipAttachmentsPreload={(currentMessage.attachments ?? 0) === 0}
bind:objectId={_id}
{shouldSaveDraft}
{boundary}
{autofocus}
on:message={onMessage}
on:update={onUpdate}
on:focus
on:blur
bind:loading
/>