mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-23 20:13:20 +00:00
Gmail attachments (#1095)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
35b8fbe9c5
commit
906f741d4b
@ -33,6 +33,7 @@
|
||||
"@anticrm/model-contact": "~0.6.1",
|
||||
"@anticrm/gmail": "~0.6.0",
|
||||
"@anticrm/gmail-resources": "~0.6.0",
|
||||
"@anticrm/model-attachment": "~0.6.0",
|
||||
"@anticrm/setting": "~0.6.0",
|
||||
"@anticrm/ui": "~0.6.0"
|
||||
}
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
import activity from '@anticrm/activity'
|
||||
import { Domain, IndexKind, Type } from '@anticrm/core'
|
||||
import type { Message, SharedMessage, SharedMessages } from '@anticrm/gmail'
|
||||
import { ArrOf, Builder, Index, Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
|
||||
import type { Message, NewMessage, SharedMessage, SharedMessages } from '@anticrm/gmail'
|
||||
import { ArrOf, Builder, Collection, Index, Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import core, { TDoc, TAttachedDoc } from '@anticrm/model-core'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import setting from '@anticrm/setting'
|
||||
import gmail from './plugin'
|
||||
|
||||
@ -46,10 +47,6 @@ export class TMessage extends TAttachedDoc implements Message {
|
||||
@Index(IndexKind.FullText)
|
||||
to!: string
|
||||
|
||||
@Prop(TypeString(), contact.string.Contact)
|
||||
@Index(IndexKind.FullText)
|
||||
contact!: string
|
||||
|
||||
@Prop(TypeString(), gmail.string.Subject)
|
||||
@Index(IndexKind.FullText)
|
||||
subject!: string
|
||||
@ -67,6 +64,37 @@ export class TMessage extends TAttachedDoc implements Message {
|
||||
|
||||
@Prop(TypeBoolean(), gmail.string.Incoming)
|
||||
incoming!: boolean
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
@Model(gmail.class.NewMessage, core.class.Doc, DOMAIN_GMAIL)
|
||||
export class TNewMessage extends TDoc implements NewMessage {
|
||||
@Prop(TypeString(), gmail.string.ReplyTo)
|
||||
@Index(IndexKind.FullText)
|
||||
replyTo?: string
|
||||
|
||||
@Prop(TypeString(), gmail.string.To)
|
||||
@Index(IndexKind.FullText)
|
||||
to!: string
|
||||
|
||||
@Prop(TypeString(), gmail.string.Subject)
|
||||
@Index(IndexKind.FullText)
|
||||
subject!: string
|
||||
|
||||
@Prop(TypeString(), gmail.string.Message)
|
||||
@Index(IndexKind.FullText)
|
||||
content!: string
|
||||
|
||||
@Prop(TypeString(), gmail.string.Status)
|
||||
status!: 'new' | 'sent'
|
||||
|
||||
@Prop(ArrOf(TypeString()), gmail.string.Copy)
|
||||
copy?: string[]
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
@Model(gmail.class.SharedMessages, core.class.AttachedDoc, DOMAIN_GMAIL)
|
||||
@ -76,7 +104,7 @@ export class TSharedMessages extends TAttachedDoc implements SharedMessages {
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TMessage, TSharedMessages)
|
||||
builder.createModel(TMessage, TSharedMessages, TNewMessage)
|
||||
|
||||
builder.createDoc(
|
||||
contact.class.ChannelProvider,
|
||||
|
@ -33,6 +33,7 @@ export default mergeIds(gmailId, gmail, {
|
||||
Messages: '' as IntlString,
|
||||
Incoming: '' as IntlString,
|
||||
Email: '' as IntlString,
|
||||
Status: '' as IntlString,
|
||||
EmailPlaceholder: '' as IntlString
|
||||
},
|
||||
ids: {
|
||||
|
@ -24,7 +24,7 @@
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||
import { IconClose } from '@anticrm/ui'
|
||||
import ActionIcon from '@anticrm/ui/src/components/ActionIcon.svelte';
|
||||
import { ActionIcon } from '@anticrm/ui'
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
|
@ -24,7 +24,7 @@ import Photos from './components/Photos.svelte'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import { uploadFile, deleteFile } from './utils'
|
||||
|
||||
export { Attachments, AttachmentsPresenter, AttachmentRefInput, AttachmentList, AttachmentDocList }
|
||||
export { Attachments, AttachmentsPresenter, AttachmentPresenter, AttachmentRefInput, AttachmentList, AttachmentDocList }
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
|
@ -27,6 +27,7 @@
|
||||
"Messages": "Messages",
|
||||
"Incoming": "Incoming",
|
||||
"Email": "Email",
|
||||
"Status": "Status",
|
||||
"EmailPlaceholder": "john.appleseed@apple.com"
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@
|
||||
"Messages": "Сообщения",
|
||||
"Incoming": "Входящие",
|
||||
"Email": "Email",
|
||||
"Status": "Статус",
|
||||
"EmailPlaceholder": "john.appleseed@apple.com"
|
||||
}
|
||||
}
|
@ -42,6 +42,8 @@
|
||||
"@anticrm/chunter": "~0.6.0",
|
||||
"@anticrm/chunter-resources": "~0.6.0",
|
||||
"@anticrm/notification-resources": "~0.6.0",
|
||||
"@anticrm/attachment": "~0.6.0",
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/login": "~0.6.1",
|
||||
"@anticrm/core": "~0.6.11"
|
||||
}
|
||||
|
@ -20,6 +20,9 @@
|
||||
import { IconArrowLeft, Label } from '@anticrm/ui'
|
||||
import gmail from '../plugin'
|
||||
import FullMessageContent from './FullMessageContent.svelte'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import attachment, { Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentPresenter } from '@anticrm/attachment-resources'
|
||||
|
||||
export let currentMessage: SharedMessage
|
||||
export let newMessage: boolean
|
||||
@ -29,6 +32,13 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const query = createQuery()
|
||||
let attachments: Attachment[] = []
|
||||
|
||||
$: currentMessage._id && query.query(attachment.class.Attachment, {
|
||||
attachedTo: currentMessage._id
|
||||
}, (res) => attachments = res)
|
||||
|
||||
$: title = currentMessage.incoming ? currentMessage.sender : currentMessage.receiver
|
||||
$: user = currentMessage.incoming ? currentMessage.receiver : currentMessage.sender
|
||||
</script>
|
||||
@ -60,13 +70,22 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col clear-mins right-content">
|
||||
<div class="flex-col clear-mins content">
|
||||
<Label label={currentMessage.incoming ? gmail.string.To : gmail.string.From} />
|
||||
{user}
|
||||
{#if currentMessage.copy?.length}
|
||||
<Label label={gmail.string.Copy} />: {currentMessage.copy.join(', ')}
|
||||
{/if}
|
||||
<div class="flex-col h-full clear-mins mt-9">
|
||||
{#if attachments.length}
|
||||
<div class='flex-row-center list mt-2'>
|
||||
{#each attachments as attachment}
|
||||
<div class='item flex'>
|
||||
<AttachmentPresenter value={attachment} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-col h-full clear-mins mt-4">
|
||||
<FullMessageContent content={currentMessage.content} />
|
||||
</div>
|
||||
</div>
|
||||
@ -90,8 +109,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.right-content {
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
padding: 1.5rem 2.5rem;
|
||||
|
||||
.list {
|
||||
padding: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
|
||||
.item + .item {
|
||||
padding-left: 1rem;
|
||||
border-left: 1px solid var(--theme-bg-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -15,6 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { SharedMessage } from '@anticrm/gmail'
|
||||
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
|
||||
import { CheckBox, Label } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getTime } from '../utils'
|
||||
@ -41,7 +42,10 @@
|
||||
<div class="content-trans-color overflow-label mr-4">
|
||||
<Label label={gmail.string.From} /><span class="content-accent-color">{message.sender}</span>
|
||||
</div>
|
||||
<div class="content-trans-color">{getTime(message.modifiedOn)}</div>
|
||||
<div class="content-trans-color flex">
|
||||
<AttachmentsPresenter value={message} />
|
||||
{getTime(message.modifiedOn)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-trans-color text-sm overflow-label mr-4 mb-4">
|
||||
<Label label={gmail.string.To} /><span class="content-accent-color">{message.receiver}</span>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
// Copyright © 2022 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
|
||||
@ -14,54 +13,112 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
import login from '@anticrm/login'
|
||||
import { NewMessage, SharedMessage } from '@anticrm/gmail'
|
||||
import EditBox from '@anticrm/ui/src/components/EditBox.svelte'
|
||||
import Button from '@anticrm/ui/src/components/Button.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { IconArrowLeft, Label } from '@anticrm/ui'
|
||||
import { Channel, Contact, formatName } from '@anticrm/contact'
|
||||
import { TextEditor } from '@anticrm/text-editor'
|
||||
import plugin from '../plugin'
|
||||
import attachmentP,{ Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentPresenter } from '@anticrm/attachment-resources'
|
||||
import { Channel,Contact,formatName } from '@anticrm/contact'
|
||||
import { Data,generateId } from '@anticrm/core'
|
||||
import { NewMessage,SharedMessage } from '@anticrm/gmail'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { getResource,setPlatformStatus,unknownError } from '@anticrm/platform'
|
||||
import { createQuery,getClient } from '@anticrm/presentation'
|
||||
import { TextEditor } from '@anticrm/text-editor'
|
||||
import { ActionIcon,IconArrowLeft,IconAttachment,IconClose,Label } from '@anticrm/ui'
|
||||
import Button from '@anticrm/ui/src/components/Button.svelte'
|
||||
import EditBox from '@anticrm/ui/src/components/EditBox.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
|
||||
export let object: Contact
|
||||
export let channel: Channel
|
||||
export let currentMessage: SharedMessage | undefined
|
||||
const client = getClient()
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
let objectId = generateId()
|
||||
|
||||
let editor: TextEditor
|
||||
let copy: string = ''
|
||||
|
||||
const obj: NewMessage = {
|
||||
const obj: Data<NewMessage> = {
|
||||
subject: currentMessage ? 'RE: ' + currentMessage.subject : '',
|
||||
content: '',
|
||||
to: channel.value,
|
||||
replyTo: currentMessage?.messageId
|
||||
replyTo: currentMessage?.messageId,
|
||||
status: 'new'
|
||||
}
|
||||
|
||||
const url = getMetadata(login.metadata.GmailUrl) ?? ''
|
||||
|
||||
async function sendMsg () {
|
||||
await fetch(url + '/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...obj,
|
||||
copy: copy.split(',').map((m) => m.trim())
|
||||
})
|
||||
})
|
||||
await client.createDoc(plugin.class.NewMessage, plugin.space.Gmail, {
|
||||
...obj,
|
||||
attachments: attachments.length,
|
||||
copy: copy.split(',').map((m) => m.trim()).filter((m) => m.length)
|
||||
}, objectId)
|
||||
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
|
||||
objectId = generateId()
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let inputFile: HTMLInputElement
|
||||
|
||||
function fileSelected () {
|
||||
const list = inputFile.files
|
||||
if (list === null || list.length === 0) return
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) createAttachment(file)
|
||||
}
|
||||
}
|
||||
|
||||
function fileDrop (e: DragEvent) {
|
||||
const list = e.dataTransfer?.files
|
||||
if (list === undefined || list.length === 0) return
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) createAttachment(file)
|
||||
}
|
||||
}
|
||||
|
||||
async function createAttachment (file: File) {
|
||||
try {
|
||||
const uploadFile = await getResource(attachmentP.helper.UploadFile)
|
||||
const uuid = await uploadFile(file, { space: plugin.space.Gmail, attachedTo: objectId })
|
||||
console.log('uploaded file uuid', uuid)
|
||||
await client.addCollection(attachmentP.class.Attachment, plugin.space.Gmail, objectId, plugin.class.NewMessage, 'attachments', {
|
||||
name: file.name,
|
||||
file: uuid,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified
|
||||
})
|
||||
} catch (err: any) {
|
||||
setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
async function removeAttachment (attachment: Attachment): Promise<void> {
|
||||
const deleteFile = await getResource(attachmentP.helper.DeleteFile)
|
||||
await client.removeCollection(attachment._class, attachment.space, attachment._id, attachment.attachedTo, attachment.attachedToClass, 'attachments')
|
||||
await deleteFile(attachment.file)
|
||||
}
|
||||
|
||||
let attachments: Attachment[] = []
|
||||
|
||||
$: objectId && query.query(attachmentP.class.Attachment, {
|
||||
attachedTo: objectId
|
||||
}, (res) => attachments = res)
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
multiple
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
style="display: none"
|
||||
on:change={fileSelected}
|
||||
/>
|
||||
<div class="flex-between clear-mins header">
|
||||
<div
|
||||
class="flex-center icon"
|
||||
@ -78,11 +135,18 @@
|
||||
<span class="content-accent-color">{formatName(object.name)} ({channel.value})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<div class="mr-3 flex-row-center">
|
||||
<div class="mr-2">
|
||||
<ActionIcon icon={IconAttachment} size={'small'} action={() => {inputFile.click()}} />
|
||||
</div>
|
||||
<Button label={plugin.string.Send} size={'small'} primary on:click={sendMsg} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col clear-mins right-content">
|
||||
<div class="flex-col clear-mins right-content"
|
||||
on:dragover|preventDefault={() => {}}
|
||||
on:dragleave={() => {}}
|
||||
on:drop|preventDefault|stopPropagation={fileDrop}
|
||||
>
|
||||
<div class="mb-2">
|
||||
<EditBox
|
||||
label={plugin.string.Subject}
|
||||
@ -91,7 +155,7 @@
|
||||
maxWidth={'min-content'}
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div>
|
||||
<EditBox
|
||||
label={plugin.string.Copy}
|
||||
bind:value={copy}
|
||||
@ -99,7 +163,19 @@
|
||||
maxWidth={'min-content'}
|
||||
/>
|
||||
</div>
|
||||
<div class="input clear-mins">
|
||||
{#if attachments.length}
|
||||
<div class='flex-row-center list mt-2'>
|
||||
{#each attachments as attachment}
|
||||
<div class='item flex'>
|
||||
<AttachmentPresenter value={attachment} />
|
||||
<div class='remove'>
|
||||
<ActionIcon icon={IconClose} action={() => { removeAttachment(attachment) }} size='small' />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="input mt-4 clear-mins">
|
||||
<TextEditor bind:this={editor} bind:content={obj.content} on:blur={editor.submit} />
|
||||
</div>
|
||||
</div>
|
||||
@ -127,6 +203,33 @@
|
||||
flex-grow: 1;
|
||||
padding: 1.5rem 2.5rem;
|
||||
|
||||
.list {
|
||||
padding: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
|
||||
.item + .item {
|
||||
padding-left: 1rem;
|
||||
border-left: 1px solid var(--theme-bg-accent-color);
|
||||
}
|
||||
|
||||
.item {
|
||||
.remove {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
.remove {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
|
@ -22,7 +22,7 @@ import type { IntegrationType, Handler } from '@anticrm/setting'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Message extends NewMessage, AttachedDoc {
|
||||
export interface Message extends BaseMessage, AttachedDoc {
|
||||
messageId: string
|
||||
from: string
|
||||
textContent: string
|
||||
@ -32,12 +32,20 @@ export interface Message extends NewMessage, AttachedDoc {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NewMessage {
|
||||
export interface BaseMessage extends Doc {
|
||||
replyTo?: string
|
||||
to: string
|
||||
subject: string
|
||||
content: string
|
||||
copy?: string[]
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NewMessage extends BaseMessage {
|
||||
status: 'new' | 'sent'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,6 +59,7 @@ export interface SharedMessage extends Doc {
|
||||
receiver: string
|
||||
incoming: boolean
|
||||
textContent: string
|
||||
attachments?: number
|
||||
copy?: string[]
|
||||
}
|
||||
|
||||
@ -80,6 +89,7 @@ export default plugin(gmailId, {
|
||||
},
|
||||
class: {
|
||||
Message: '' as Ref<Class<Message>>,
|
||||
NewMessage: '' as Ref<Class<NewMessage>>,
|
||||
SharedMessages: '' as Ref<Class<SharedMessages>>,
|
||||
SharedMessage: '' as Ref<Class<SharedMessage>>
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user