Files
huly-platform/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte
T
2024-09-27 01:34:25 +07:00

310 lines
10 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 contact, { Person, PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
import { Class, Doc, getCurrentAccount, Markup, Ref, Space, WithLookup } from '@hcengineering/core'
import { getClient, MessageViewer } from '@hcengineering/presentation'
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
import { getDocLinkTitle } from '@hcengineering/view-resources'
import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui'
import view from '@hcengineering/view'
import activity, { ActivityMessage, ActivityMessageViewType, DisplayActivityMessage } from '@hcengineering/activity'
import { ActivityDocLink, ActivityMessageTemplate, MessageInlineAction } from '@hcengineering/activity-resources'
import chunter, { ChatMessage, ChatMessageViewlet, InlineButton } from '@hcengineering/chunter'
import { Attachment } from '@hcengineering/attachment'
import { EmptyMarkup } from '@hcengineering/text'
import ChatMessageHeader from './ChatMessageHeader.svelte'
import ChatMessageInput from './ChatMessageInput.svelte'
import InlineButtons from '../InlineButtons.svelte'
import { translatedMessagesStore, translatingMessagesStore, shownTranslatedMessagesStore } from '../../stores'
export let value: WithLookup<ChatMessage> | undefined
export let doc: Doc | undefined = undefined
export let showNotify: boolean = false
export let isHighlighted: boolean = false
export let isSelected: boolean = false
export let shouldScroll: boolean = false
export let embedded: boolean = false
export let withActions: boolean = true
export let showEmbedded = false
export let hideFooter = false
export let skipLabel = false
export let actions: Action[] = []
export let hoverable = true
export let inline = false
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
export let withShowMore: boolean = true
export let attachmentImageSize: AttachmentImageSize = 'auto'
export let videoPreload = true
export let hideLink = false
export let compact = false
export let readonly = false
export let type: ActivityMessageViewType = 'default'
export let onClick: (() => void) | undefined = undefined
const client = getClient()
const { pendingCreatedDocs } = client
const hierarchy = client.getHierarchy()
const STALE_TIMEOUT_MS = 5000
const currentAccount = getCurrentAccount()
let account: PersonAccount | undefined = undefined
let person: Person | undefined = undefined
let parentMessage: DisplayActivityMessage | undefined = undefined
let object: Doc | undefined
let refInput: ChatMessageInput
let viewlet: ChatMessageViewlet | undefined
;[viewlet] =
value !== undefined
? client.getModel().findAllSync(chunter.class.ChatMessageViewlet, {
objectClass: value.attachedToClass,
messageClass: value._class
})
: []
$: accountId = value?.createdBy
$: account = accountId !== undefined ? $personAccountByIdStore.get(accountId as Ref<PersonAccount>) : undefined
$: person = account?.person !== undefined ? $personByIdStore.get(account.person) : undefined
let originalText = value?.message
$: if (value && originalText && originalText !== value?.message) {
originalText = value.message
translatedMessagesStore.update((map) => {
if (value) {
map.delete(value._id)
}
return map
})
shownTranslatedMessagesStore.update((map) => {
if (value) {
map.delete(value._id)
}
return map
})
} else {
originalText = value?.message
}
$: value !== undefined &&
getParentMessage(value.attachedToClass, value.attachedTo, value.space).then((res) => {
parentMessage = res as DisplayActivityMessage
})
$: if (doc !== undefined && value?.attachedTo === doc._id) {
object = doc
} else if (value !== undefined) {
void client.findOne(value.attachedToClass, { _id: value.attachedTo }).then((result) => {
object = result
})
}
let stale = false
let markStaleId: NodeJS.Timeout | undefined
$: pending = value?._id !== undefined && $pendingCreatedDocs[value._id]
$: if (pending) {
markStaleId = setTimeout(() => {
stale = true
}, STALE_TIMEOUT_MS)
} else {
if (markStaleId !== undefined) {
clearTimeout(markStaleId)
markStaleId = undefined
}
stale = false
}
async function getParentMessage (
_class: Ref<Class<Doc>>,
_id: Ref<Doc>,
space: Ref<Space>
): Promise<ActivityMessage | undefined> {
if (hierarchy.isDerived(_class, activity.class.ActivityMessage)) {
return await client.findOne<ActivityMessage>(_class, { _id: _id as Ref<ActivityMessage>, space })
}
}
async function handleEditAction (): Promise<void> {
isEditing = true
}
let isEditing = false
let additionalActions: Action[] = []
$: isOwn = account !== undefined && account._id === currentAccount._id
$: additionalActions = [
...(isOwn
? [
{
label: activity.string.Edit,
icon: IconEdit,
group: 'edit',
action: handleEditAction
}
]
: []),
...actions
]
let attachments: Attachment[] | undefined = undefined
$: attachments = value?.$lookup?.attachments as Attachment[] | undefined
let inlineButtons: InlineButton[] = []
$: inlineButtons = (value?.$lookup?.inlineButtons ?? []) as InlineButton[]
let inlineActions: MessageInlineAction[] = []
$: updateInlineActions($translatingMessagesStore, $shownTranslatedMessagesStore)
function updateInlineActions (
translatingMessages: Set<Ref<ChatMessage>>,
shownTranslated: Set<Ref<ChatMessage>>
): void {
if (value === undefined) {
inlineActions = []
return
}
const result: MessageInlineAction[] = []
if (translatingMessages.has(value._id)) {
result.push({
label: chunter.string.Translating
})
} else if (shownTranslated.has(value._id)) {
result.push({
label: chunter.string.ShowOriginal,
onClick: async () => {
shownTranslatedMessagesStore.update((map) => {
if (value) {
map.delete(value._id)
}
return map
})
}
})
}
inlineActions = result
}
$: socialProvider = value?.provider
? client.getModel().findAllSync(contact.class.ChannelProvider, { _id: value.provider })[0]
: undefined
let displayText: Markup = value?.message ?? EmptyMarkup
$: if (value && $shownTranslatedMessagesStore.has(value._id)) {
displayText = $translatedMessagesStore.get(value._id) ?? value?.message ?? EmptyMarkup
} else {
displayText = value?.message ?? EmptyMarkup
}
</script>
{#if inline && object}
{#await getDocLinkTitle(client, object._id, object._class, object) then title}
<ActivityDocLink
{object}
{title}
panelComponent={hierarchy.classHierarchyMixin(object._class, view.mixin.ObjectPanel)?.component ??
view.component.EditDoc}
/>
{/await}
{:else if value && !inline}
<ActivityMessageTemplate
message={value}
{viewlet}
{parentMessage}
{person}
{showNotify}
{isHighlighted}
{isSelected}
{shouldScroll}
{embedded}
withActions={withActions && !isEditing}
actions={additionalActions}
{showEmbedded}
{hideFooter}
{hoverable}
{hoverStyles}
{skipLabel}
{pending}
{stale}
{readonly}
excludedActions={$shownTranslatedMessagesStore.has(value._id)
? [chunter.action.TranslateMessage]
: [chunter.action.ShowOriginalMessage]}
socialIcon={socialProvider?.icon}
showDatePreposition={hideLink}
{inlineActions}
{type}
{onClick}
>
<svelte:fragment slot="header">
<ChatMessageHeader label={viewlet?.label} />
</svelte:fragment>
<svelte:fragment slot="content">
{#if !isEditing}
{#if withShowMore}
<ShowMore limit={compact ? 80 : undefined}>
<div class="clear-mins">
<MessageViewer message={displayText} />
{#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
<div class="mt-2" />
{/if}
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} {videoPreload} />
<InlineButtons {value} {inlineButtons} />
</div>
</ShowMore>
{:else}
<div class="clear-mins">
<MessageViewer message={displayText} />
{#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
<div class="mt-2" />
{/if}
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} {videoPreload} />
<InlineButtons {value} {inlineButtons} />
</div>
{/if}
{:else if object}
<ChatMessageInput
bind:this={refInput}
chatMessage={value}
shouldSaveDraft={false}
focusIndex={1000}
autofocus
{object}
on:submit={() => {
isEditing = false
}}
/>
<div class="flex-row-center gap-2 justify-end mt-2">
<Button
label={view.string.Cancel}
on:click={() => {
isEditing = false
}}
/>
<Button label={activity.string.Update} accent on:click={() => refInput.submit()} />
</div>
{/if}
</svelte:fragment>
</ActivityMessageTemplate>
{/if}