mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-27 01:13:27 +03:00
Attachments in comments (#1077)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
0d7857fe99
commit
2a061d58ec
@ -29,6 +29,7 @@
|
||||
"@anticrm/model": "~0.6.0",
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/model-attachment": "~0.6.0",
|
||||
"@anticrm/chunter": "~0.6.0",
|
||||
"@anticrm/chunter-resources": "~0.6.0",
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
|
@ -17,12 +17,13 @@ import activity from '@anticrm/activity'
|
||||
import type { Backlink, Channel, Comment, Message } from '@anticrm/chunter'
|
||||
import type { Class, Doc, Domain, Ref } from '@anticrm/core'
|
||||
import { IndexKind } from '@anticrm/core'
|
||||
import { Builder, Index, Model, Prop, TypeMarkup, UX } from '@anticrm/model'
|
||||
import { Builder, Collection, Index, Model, Prop, TypeMarkup, UX } from '@anticrm/model'
|
||||
import core, { TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import { ObjectDDParticipant } from '@anticrm/view'
|
||||
import chunter from './plugin'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
|
||||
export const DOMAIN_CHUNTER = 'chunter' as Domain
|
||||
export const DOMAIN_COMMENT = 'comment' as Domain
|
||||
@ -36,6 +37,9 @@ export class TMessage extends TDoc implements Message {
|
||||
@Prop(TypeMarkup(), chunter.string.Content)
|
||||
@Index(IndexKind.FullText)
|
||||
content!: string
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
@Model(chunter.class.Comment, core.class.AttachedDoc, DOMAIN_COMMENT)
|
||||
@ -44,6 +48,9 @@ export class TComment extends TAttachedDoc implements Comment {
|
||||
@Prop(TypeMarkup(), chunter.string.Message)
|
||||
@Index(IndexKind.FullText)
|
||||
message!: string
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
@Model(chunter.class.Backlink, chunter.class.Comment)
|
||||
|
@ -34,6 +34,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
export let content: string = ''
|
||||
export let showSend = true
|
||||
export let withoutTopBorder = false
|
||||
const client = getClient()
|
||||
|
||||
let textEditor: TextEditor
|
||||
@ -53,7 +54,7 @@
|
||||
{
|
||||
label: textEditorPlugin.string.Attach,
|
||||
icon: Attach,
|
||||
action: () => {},
|
||||
action: () => { dispatch('attach') },
|
||||
order: 1000
|
||||
},
|
||||
{
|
||||
@ -156,7 +157,7 @@
|
||||
</script>
|
||||
|
||||
<div class="ref-container">
|
||||
<div class="textInput">
|
||||
<div class="textInput" class:withoutTopBorder>
|
||||
<div class="inputMsg">
|
||||
<TextEditor bind:content={content} bind:this={textEditor} on:content={
|
||||
ev => {
|
||||
@ -195,6 +196,11 @@
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
|
||||
&.withoutTopBorder {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.inputMsg {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/view-resources": "~0.6.0",
|
||||
"@anticrm/panel": "~0.6.0",
|
||||
"@anticrm/text-editor": "~0.6.0",
|
||||
"@anticrm/login": "~0.6.1",
|
||||
"filesize": "^8.0.3"
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
<!--
|
||||
// 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
|
||||
// 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 { Attachment } from '@anticrm/attachment'
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import attachment from '../plugin'
|
||||
import AttachmentList from './AttachmentList.svelte'
|
||||
|
||||
export let value: Doc & { attachments?: number }
|
||||
|
||||
const query = createQuery()
|
||||
let attachments: Attachment[] = []
|
||||
|
||||
$: updateQuery(value)
|
||||
|
||||
function updateQuery (value: Doc & { attachments?: number }): void {
|
||||
if (value && value.attachments && value.attachments > 0) {
|
||||
query.query(attachment.class.Attachment, {
|
||||
attachedTo: value._id
|
||||
}, (res) => attachments = res)
|
||||
} else {
|
||||
attachments = []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<AttachmentList {attachments} />
|
@ -0,0 +1,47 @@
|
||||
<!--
|
||||
// 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
|
||||
// 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 { Attachment } from '@anticrm/attachment'
|
||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||
|
||||
export let attachments: Attachment[] = []
|
||||
</script>
|
||||
|
||||
{#if attachments.length}
|
||||
<div class='container'>
|
||||
{#each attachments as attachment}
|
||||
<div class='item'>
|
||||
<AttachmentPresenter value={attachment} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: 0.75rem;
|
||||
|
||||
.item {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.item + .item {
|
||||
border-top: 1px solid var(--theme-bg-accent-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,156 @@
|
||||
|
||||
<!--
|
||||
// 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
|
||||
// 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 { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { ReferenceInput } from '@anticrm/text-editor'
|
||||
import { deleteFile, uploadFile } from '../utils'
|
||||
import attachment from '../plugin'
|
||||
import { setPlatformStatus, unknownError } from '@anticrm/platform'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { Class, Doc, Ref, Space } from '@anticrm/core'
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||
import { IconClose } from '@anticrm/ui'
|
||||
import ActionIcon from '@anticrm/ui/src/components/ActionIcon.svelte';
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
|
||||
let inputFile: HTMLInputElement
|
||||
let saved = false
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
let attachments: Attachment[] = []
|
||||
|
||||
$: objectId && query.query(attachment.class.Attachment, {
|
||||
attachedTo: objectId
|
||||
}, (res) => attachments = res)
|
||||
|
||||
async function createAttachment (file: File) {
|
||||
try {
|
||||
const uuid = await uploadFile(file, { space, attachedTo: objectId })
|
||||
console.log('uploaded file uuid', uuid)
|
||||
await client.addCollection(attachment.class.Attachment, space, objectId, _class, 'attachments', {
|
||||
name: file.name,
|
||||
file: uuid,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified
|
||||
})
|
||||
} catch (err: any) {
|
||||
setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
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 removeAttachment (attachment: Attachment): Promise<void> {
|
||||
await client.removeCollection(attachment._class, attachment.space, attachment._id, attachment.attachedTo, attachment.attachedToClass, 'attachments')
|
||||
await deleteFile(attachment.file)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if (!saved) {
|
||||
attachments.map((attachment) => {
|
||||
removeAttachment(attachment)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
saved = true
|
||||
dispatch('message', { message: event.detail, attachments: attachments.length })
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
multiple
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
style="display: none"
|
||||
on:change={fileSelected}
|
||||
/>
|
||||
<div class="container"
|
||||
on:dragover|preventDefault={() => {}}
|
||||
on:dragleave={() => {}}
|
||||
on:drop|preventDefault|stopPropagation={fileDrop}
|
||||
>
|
||||
{#if attachments.length}
|
||||
<div class='flex-row-center list'>
|
||||
{#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}
|
||||
<ReferenceInput on:message={onMessage} withoutTopBorder={attachments.length > 0} on:attach={() => { inputFile.click() }} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.list {
|
||||
padding: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
overflow-x: auto;
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: .75rem;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
.item + .item {
|
||||
padding-left: 1rem;
|
||||
border-left: 1px solid var(--theme-bg-accent-color);
|
||||
}
|
||||
|
||||
.item {
|
||||
.remove {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
.remove {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -15,13 +15,16 @@
|
||||
|
||||
import AttachmentsPresenter from './components/AttachmentsPresenter.svelte'
|
||||
import AttachmentPresenter from './components/AttachmentPresenter.svelte'
|
||||
import AttachmentDocList from './components/AttachmentDocList.svelte'
|
||||
import AttachmentList from './components/AttachmentList.svelte'
|
||||
import AttachmentRefInput from './components/AttachmentRefInput.svelte'
|
||||
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
|
||||
import Attachments from './components/Attachments.svelte'
|
||||
import Photos from './components/Photos.svelte'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import { uploadFile, deleteFile } from './utils'
|
||||
|
||||
export { Attachments, AttachmentsPresenter }
|
||||
export { Attachments, AttachmentsPresenter, AttachmentRefInput, AttachmentList, AttachmentDocList }
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
|
@ -41,6 +41,8 @@
|
||||
"@anticrm/text-editor": "~0.6.0",
|
||||
"@anticrm/contact": "~0.6.2",
|
||||
"@anticrm/contact-resources": "~0.6.0",
|
||||
"@anticrm/attachment": "~0.6.0",
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/view-resources": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/workbench": "~0.6.1"
|
||||
|
@ -17,6 +17,7 @@
|
||||
import type { Ref, Space } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
import attachment from '@anticrm/attachment'
|
||||
import chunter from '../plugin'
|
||||
|
||||
import { default as MessageComponent } from './Message.svelte'
|
||||
@ -26,7 +27,7 @@
|
||||
let messages: Message[] | undefined
|
||||
const query = createQuery()
|
||||
|
||||
$: query.query(chunter.class.Message, { space }, result => { messages = result })
|
||||
$: query.query(chunter.class.Message, { space }, result => { messages = result }, { lookup: { _id: { attachments: attachment.class.Attachment } }})
|
||||
</script>
|
||||
|
||||
<div class="flex-col container">
|
||||
|
@ -14,25 +14,30 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Ref, Space } from '@anticrm/core'
|
||||
import { generateId, Ref, Space } from '@anticrm/core'
|
||||
import chunter from '../plugin'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
|
||||
import Channel from './Channel.svelte'
|
||||
import { ReferenceInput } from '@anticrm/text-editor'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
const client = getClient()
|
||||
const _class = chunter.class.Message
|
||||
let _id = generateId()
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const msgRef = await client.createDoc(chunter.class.Message, space, {
|
||||
content: event.detail
|
||||
})
|
||||
const { message, attachments } = event.detail
|
||||
await client.createDoc(_class, space, {
|
||||
content: message,
|
||||
attachments
|
||||
}, _id)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.Channel, msgRef, event.detail)
|
||||
await createBacklinks(client, space, chunter.class.Channel, _id, message)
|
||||
_id = generateId()
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -40,7 +45,7 @@
|
||||
<Channel {space} />
|
||||
</div>
|
||||
<div class="reference">
|
||||
<ReferenceInput on:message={onMessage}/>
|
||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage}/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -16,21 +16,25 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Comment } from '@anticrm/chunter'
|
||||
import { Doc } from '@anticrm/core'
|
||||
import { Doc, generateId, Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { ReferenceInput } from '@anticrm/text-editor'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
|
||||
const client = getClient()
|
||||
export let object: Doc
|
||||
const _class = chunter.class.Comment
|
||||
let _id: Ref<Comment> = generateId()
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const commentId = await client.addCollection<Doc, Comment>(chunter.class.Comment, object.space, object._id, object._class, 'comments', { message: event.detail })
|
||||
const { message, attachments } = event.detail
|
||||
await client.addCollection<Doc, Comment>(_class, object.space, object._id, object._class, 'comments', { message, attachments }, _id)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, object._id, object._class, commentId, event.detail)
|
||||
await createBacklinks(client, object._id, object._class, _id, message)
|
||||
_id = generateId()
|
||||
}
|
||||
|
||||
</script>
|
||||
<ReferenceInput on:message={onMessage} />
|
||||
<AttachmentRefInput {_class} space={object.space} objectId={_id} on:message={onMessage}/>
|
@ -26,14 +26,19 @@
|
||||
import { MessageViewer } from '@anticrm/presentation'
|
||||
import { getTime, getUser } from '../utils'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
import { AttachmentList } from '@anticrm/attachment-resources'
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
|
||||
export let message: Message
|
||||
export let message: WithLookup<Message>
|
||||
|
||||
let reactions: boolean = false
|
||||
let replies: boolean = false
|
||||
let thread: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
@ -46,6 +51,7 @@
|
||||
<span>{getTime(message.modifiedOn)}</span>
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={message.content}/></div>
|
||||
<div class="attachments"><AttachmentList {attachments} /></div>
|
||||
{#if (reactions || replies) && !thread}
|
||||
<div class="footer">
|
||||
<div>{#if reactions}<Reactions/>{/if}</div>
|
||||
@ -96,6 +102,9 @@
|
||||
.text {
|
||||
line-height: 150%;
|
||||
}
|
||||
.attachments {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -17,6 +17,7 @@
|
||||
import type { Comment } from '@anticrm/chunter'
|
||||
import type { TxCreateDoc } from '@anticrm/core'
|
||||
import { getClient, MessageViewer } from '@anticrm/presentation'
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import { ReferenceInput } from '@anticrm/text-editor'
|
||||
import { Button } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@ -55,6 +56,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<MessageViewer message={value.message}/>
|
||||
<AttachmentDocList {value} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -28,6 +28,7 @@ export interface Channel extends Space {}
|
||||
*/
|
||||
export interface Message extends Doc {
|
||||
content: string
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,6 +36,7 @@ export interface Message extends Doc {
|
||||
*/
|
||||
export interface Comment extends AttachedDoc {
|
||||
message: string
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user