mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-25 19:58:30 +03:00
Chunter last views (#1273)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
f13353c9ec
commit
ed1c29573f
@ -33,4 +33,8 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(chunter.class.Comment, core.class.Class, serverCore.mixin.ObjectDDParticipant, {
|
||||
collectDocs: serverChunter.function.CommentRemove
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverChunter.trigger.CommentCreate
|
||||
})
|
||||
}
|
||||
|
@ -304,6 +304,7 @@ p:last-child { margin-block-end: 0; }
|
||||
.mb-2 { margin-bottom: .5rem; }
|
||||
.mb-3 { margin-bottom: .75rem; }
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mb-6 { margin-bottom: 1.5rem; }
|
||||
.mx-1 { margin: 0 .25rem; }
|
||||
.mx-2 { margin: 0 .5rem; }
|
||||
.mx-3 { margin: 0 .75rem; }
|
||||
|
@ -24,6 +24,7 @@
|
||||
"Replies": "Replies",
|
||||
"LastReply": "Last reply",
|
||||
"RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}",
|
||||
"Thread": "Thread"
|
||||
"Thread": "Thread",
|
||||
"New": "New"
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
"Replies": "Ответы",
|
||||
"LastReply": "Последний ответ",
|
||||
"RepliesCount": "{replies, plural, =1 {# ответ} =2 {# ответа} =3 {# ответа} =4 {# ответа} other {# ответов}}",
|
||||
"Thread": "Обсуждение"
|
||||
"Thread": "Обсуждение",
|
||||
"New": "Новое"
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@
|
||||
"@anticrm/text-editor": "~0.6.0",
|
||||
"@anticrm/contact": "~0.6.5",
|
||||
"@anticrm/contact-resources": "~0.6.0",
|
||||
"@anticrm/notification-resources": "~0.6.0",
|
||||
"@anticrm/attachment": "~0.6.1",
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/view-resources": "~0.6.0",
|
||||
|
@ -14,20 +14,37 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
import contact,{ Employee } from '@anticrm/contact'
|
||||
import { Ref,Space } from '@anticrm/core'
|
||||
import core, { Doc,Ref,Space,Timestamp, WithLookup } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { afterUpdate, beforeUpdate } from 'svelte'
|
||||
import chunter from '../plugin'
|
||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||
import MessageComponent from './Message.svelte'
|
||||
|
||||
export let space: Ref<Space> | undefined
|
||||
|
||||
let messages: Message[] | undefined
|
||||
let div: HTMLDivElement | undefined
|
||||
let autoscroll: boolean = false
|
||||
|
||||
beforeUpdate(() => {
|
||||
autoscroll = div !== undefined && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20)
|
||||
})
|
||||
|
||||
afterUpdate(() => {
|
||||
if (div && autoscroll) div.scrollTo(0, div.scrollHeight)
|
||||
})
|
||||
|
||||
let messages: WithLookup<Message>[] | undefined
|
||||
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
|
||||
const query = createQuery()
|
||||
const employeeQuery = createQuery()
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
|
||||
employeeQuery.query(contact.class.Employee, { }, (res) => employees = new Map(res.map((r) => { return [r._id, r] })))
|
||||
|
||||
$: updateQuery(space)
|
||||
@ -42,14 +59,37 @@
|
||||
space
|
||||
}, (res) => {
|
||||
messages = res
|
||||
newMessagesPos = newMessagesStart(messages)
|
||||
notificationClient.updateLastView(space, chunter.class.Channel)
|
||||
}, {
|
||||
lookup: {
|
||||
_id: { attachments: attachment.class.Attachment },
|
||||
createBy: core.class.Account
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function newMessagesStart (messages: Message[]): number {
|
||||
if (space === undefined) return -1
|
||||
const lastView = $lastViews.get(space)
|
||||
if (lastView === undefined) return -1
|
||||
for (let index = 0; index < messages.length; index++) {
|
||||
const message = messages[index]
|
||||
if (message.createOn > lastView) return index
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
let newMessagesPos: number = -1
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex-col container">
|
||||
<div class="flex-col vScroll container" bind:this={div}>
|
||||
{#if messages}
|
||||
{#each messages as message}
|
||||
{#each messages as message, i (message._id)}
|
||||
{#if newMessagesPos === i}
|
||||
<ChannelSeparator title={chunter.string.New} line reverse isNew />
|
||||
{/if}
|
||||
<MessageComponent {message} {employees} on:openThread />
|
||||
{/each}
|
||||
{/if}
|
||||
@ -57,6 +97,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
flex-shrink: 0;
|
||||
margin: 1rem 1rem 0;
|
||||
padding: 1.5rem 1.5rem 0px;
|
||||
}
|
||||
</style>
|
||||
|
@ -20,12 +20,13 @@
|
||||
export let title: IntlString
|
||||
export let line: boolean = false
|
||||
export let params: any = undefined
|
||||
|
||||
export let reverse: boolean = false
|
||||
export let isNew: boolean = false
|
||||
</script>
|
||||
|
||||
<div class="w-full flex-center whitespace-nowrap mb-4">
|
||||
<div class="w-full text-sm flex-center whitespace-nowrap mb-6" class:flex-reverse={reverse} class:new={isNew} >
|
||||
<Label label={title} {params} />
|
||||
<div class="ml-4" class:line={line} ></div>
|
||||
<div class:ml-4={!reverse} class:mr-4={reverse} class:line={line} ></div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@ -34,5 +35,12 @@
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--theme-chat-divider);
|
||||
|
||||
}
|
||||
.new {
|
||||
.line {
|
||||
background-color: var(--highlight-red);
|
||||
}
|
||||
color: var(--highlight-red);
|
||||
}
|
||||
</style>
|
||||
|
@ -17,6 +17,7 @@
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import { Message } from '@anticrm/chunter'
|
||||
import { generateId,getCurrentAccount,Ref,Space, TxFactory } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { getCurrentLocation,navigate } from '@anticrm/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
@ -28,6 +29,7 @@
|
||||
const client = getClient()
|
||||
const _class = chunter.class.Message
|
||||
let _id = generateId() as Ref<Message>
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const { message, attachments } = event.detail
|
||||
@ -40,10 +42,12 @@
|
||||
attachments
|
||||
}, _id)
|
||||
tx.attributes.createOn = tx.modifiedOn
|
||||
await notificationClient.updateLastView(space, chunter.class.Channel, tx.modifiedOn, true)
|
||||
await client.tx(tx)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.Channel, _id, message)
|
||||
|
||||
_id = generateId()
|
||||
}
|
||||
|
||||
@ -55,22 +59,12 @@
|
||||
|
||||
</script>
|
||||
|
||||
<div class="msg-board">
|
||||
<Channel {space} on:openThread={(e) => { openThread(e.detail) }} />
|
||||
</div>
|
||||
<Channel {space} on:openThread={(e) => { openThread(e.detail) }} />
|
||||
<div class="reference">
|
||||
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage}/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.msg-board {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
margin: 1rem 1rem 0;
|
||||
padding: 1.5rem 1.5rem 0px;
|
||||
overflow: auto;
|
||||
}
|
||||
.reference {
|
||||
margin: 1.25rem 2.5rem;
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { Timestamp } from "@anticrm/core"
|
||||
|
||||
export let value: Timestamp
|
||||
export let line: boolean = false
|
||||
const current = new Date()
|
||||
const target = new Date(value)
|
||||
let options: Intl.DateTimeFormatOptions = {
|
||||
day: 'numeric',
|
||||
month: 'long'
|
||||
}
|
||||
if (current.getFullYear() !== target.getFullYear()) {
|
||||
options = {
|
||||
...options,
|
||||
year: '2-digit'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-center container" class:line={line}>
|
||||
<div class="title">{new Intl.DateTimeFormat('default', options).format(value)}</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 1.75rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
padding: .375rem .75rem;
|
||||
font-weight: 600;
|
||||
font-size: .75rem;
|
||||
letter-spacing: .5;
|
||||
text-transform: uppercase;
|
||||
color: var(--theme-content-trans-color);
|
||||
z-index: 1;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1.25rem;
|
||||
background-color: var(--theme-chat-divider);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&.line {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--theme-chat-divider);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -14,10 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentList } from '@anticrm/attachment-resources'
|
||||
import type { Message } from '@anticrm/chunter'
|
||||
import contact,{ Employee,EmployeeAccount,formatName } from '@anticrm/contact'
|
||||
import { Account,Ref } from '@anticrm/core'
|
||||
import { Employee,EmployeeAccount,formatName } from '@anticrm/contact'
|
||||
import { Ref,WithLookup } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { Avatar,getClient,MessageViewer } from '@anticrm/presentation'
|
||||
import { ActionIcon,IconMoreH,Menu,showPopup } from '@anticrm/ui'
|
||||
@ -32,10 +33,13 @@
|
||||
import Reactions from './Reactions.svelte'
|
||||
import Replies from './Replies.svelte'
|
||||
|
||||
export let message: Message
|
||||
export let message: WithLookup<Message>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
export let thread: boolean = false
|
||||
|
||||
$: employee = getEmployee(message)
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -62,10 +66,11 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function getEmployee (createdBy: Ref<Account>): Promise<Employee | undefined> {
|
||||
const account = await client.findOne(contact.class.EmployeeAccount, { _id: createdBy as Ref<EmployeeAccount> })
|
||||
if (account === undefined) return
|
||||
return employees.get(account.employee)
|
||||
function getEmployee (message: WithLookup<Message>): Employee | undefined {
|
||||
const employee = (message.$lookup?.createBy as EmployeeAccount).employee
|
||||
if (employee !== undefined) {
|
||||
return employees.get(employee)
|
||||
}
|
||||
}
|
||||
|
||||
function openThread () {
|
||||
@ -74,25 +79,23 @@
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#await getEmployee(message.createBy) then employee}
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(message.createOn)}</span>
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={message.content}/></div>
|
||||
{#if message.attachments}<div class="attachments"><AttachmentDocList value={message} /></div>{/if}
|
||||
{#if (reactions || message.replies)}
|
||||
<div class="footer flex-col">
|
||||
<div>{#if reactions}<Reactions/>{/if}</div>
|
||||
{#if !thread}
|
||||
<div>{#if message.replies}<Replies replies={message.replies} lastReply={message.lastReply} on:click={openThread} />{/if}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(message.createOn)}</span>
|
||||
</div>
|
||||
{/await}
|
||||
<div class="text"><MessageViewer message={message.content}/></div>
|
||||
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
|
||||
{#if (reactions || message.replies)}
|
||||
<div class="footer flex-col">
|
||||
<div>{#if reactions}<Reactions/>{/if}</div>
|
||||
{#if !thread}
|
||||
<div>{#if message.replies}<Replies replies={message.replies} lastReply={message.lastReply} on:click={openThread} />{/if}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'} action={(e) => { showMenu(e) }}/></div>
|
||||
{#if !thread}
|
||||
|
@ -14,10 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { AttachmentDocList } from '@anticrm/attachment-resources'
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentList } from '@anticrm/attachment-resources'
|
||||
import type { Comment } from '@anticrm/chunter'
|
||||
import contact,{ Employee,EmployeeAccount,formatName } from '@anticrm/contact'
|
||||
import { Account,Ref } from '@anticrm/core'
|
||||
import { Employee,EmployeeAccount,formatName } from '@anticrm/contact'
|
||||
import { Ref,WithLookup } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { Avatar,getClient,MessageViewer } from '@anticrm/presentation'
|
||||
import { ActionIcon,IconMoreH,Menu,showPopup } from '@anticrm/ui'
|
||||
@ -29,9 +30,11 @@
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import Reactions from './Reactions.svelte'
|
||||
|
||||
export let comment: Comment
|
||||
export let comment: WithLookup<Comment>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
|
||||
$: attachments = (comment.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let reactions: boolean = false
|
||||
@ -56,30 +59,31 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function getEmployee (createdBy: Ref<Account>): Promise<Employee | undefined> {
|
||||
const account = await client.findOne(contact.class.EmployeeAccount, { _id: createdBy as Ref<EmployeeAccount> })
|
||||
if (account === undefined) return
|
||||
return employees.get(account.employee)
|
||||
$: employee = getEmployee(comment)
|
||||
|
||||
function getEmployee (comment: WithLookup<Comment>): Employee | undefined {
|
||||
const employee = (comment.$lookup?.modifiedBy as EmployeeAccount)?.employee
|
||||
if (employee !== undefined) {
|
||||
return employees.get(employee)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#await getEmployee(comment.modifiedBy) then employee}
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(comment.modifiedOn)}</span>
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={comment.message}/></div>
|
||||
{#if comment.attachments}<div class="attachments"><AttachmentDocList value={comment} /></div>{/if}
|
||||
{#if reactions}
|
||||
<div class="footer">
|
||||
<div><Reactions/></div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(comment.modifiedOn)}</span>
|
||||
</div>
|
||||
{/await}
|
||||
<div class="text"><MessageViewer message={comment.message}/></div>
|
||||
{#if comment.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
|
||||
{#if reactions}
|
||||
<div class="footer">
|
||||
<div><Reactions/></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'} action={(e) => { showMenu(e) }}/></div>
|
||||
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'}/></div>
|
||||
|
@ -16,11 +16,12 @@
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { Comment,Message } from '@anticrm/chunter'
|
||||
import contact,{ Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import { Class,generateId,getCurrentAccount,Lookup,Ref, Space } from '@anticrm/core'
|
||||
import contact,{ Employee,EmployeeAccount } from '@anticrm/contact'
|
||||
import core,{ generateId,getCurrentAccount,Ref,Space,TxFactory } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { createQuery,getClient } from '@anticrm/presentation'
|
||||
import { IconClose,Label } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { afterUpdate,beforeUpdate,createEventDispatcher } from 'svelte'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||
@ -37,8 +38,23 @@
|
||||
let message: Message | undefined
|
||||
let commentId = generateId()
|
||||
|
||||
let div: HTMLDivElement | undefined
|
||||
let autoscroll: boolean = false
|
||||
|
||||
beforeUpdate(() => {
|
||||
autoscroll = div !== undefined && (div.offsetHeight + div.scrollTop) > (div.scrollHeight - 20)
|
||||
})
|
||||
|
||||
afterUpdate(() => {
|
||||
if (div && autoscroll) div.scrollTo(0, div.scrollHeight)
|
||||
})
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
|
||||
const lookup = {
|
||||
_id: { attachments: attachment.class.Attachment }
|
||||
_id: { attachments: attachment.class.Attachment },
|
||||
modifiedBy: core.class.Account
|
||||
}
|
||||
|
||||
$: updateQueries(_id)
|
||||
@ -47,12 +63,19 @@
|
||||
messageQuery.query(chunter.class.Message, {
|
||||
_id: id
|
||||
}, (res) => message = res[0], {
|
||||
lookup
|
||||
lookup: {
|
||||
_id: { attachments: attachment.class.Attachment },
|
||||
createBy: core.class.Account
|
||||
}
|
||||
})
|
||||
|
||||
query.query(chunter.class.Comment, {
|
||||
attachedTo: id
|
||||
}, (res) => comments = res, {
|
||||
}, (res) => {
|
||||
comments = res
|
||||
newMessagesPos = newMessagesStart(comments)
|
||||
notificationClient.updateLastView(id, chunter.class.Message)
|
||||
}, {
|
||||
lookup
|
||||
})
|
||||
}
|
||||
@ -64,8 +87,9 @@
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
const { message, attachments } = event.detail
|
||||
const employee = (getCurrentAccount() as EmployeeAccount).employee
|
||||
await client.createDoc(chunter.class.Comment, space, {
|
||||
const me = getCurrentAccount()._id
|
||||
const txFactory = new TxFactory(me)
|
||||
const tx = txFactory.createTxCreateDoc(chunter.class.Comment, space, {
|
||||
attachedTo: _id,
|
||||
attachedToClass: chunter.class.Message,
|
||||
collection: 'replies',
|
||||
@ -73,39 +97,49 @@
|
||||
attachments
|
||||
}, commentId)
|
||||
|
||||
await client.updateDoc(chunter.class.Message, space, _id, {
|
||||
$push: { replies: employee },
|
||||
lastReply: new Date().getTime()
|
||||
})
|
||||
await notificationClient.updateLastView(_id, chunter.class.Message, tx.modifiedOn, true)
|
||||
await client.tx(tx)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.Channel, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
}
|
||||
let comments: Comment[] = []
|
||||
|
||||
function newMessagesStart (comments: Comment[]): number {
|
||||
const lastView = $lastViews.get(_id)
|
||||
if (lastView === undefined) return -1
|
||||
for (let index = 0; index < comments.length; index++) {
|
||||
const comment = comments[index]
|
||||
if (comment.modifiedOn > lastView) return index
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
let newMessagesPos: number = -1
|
||||
</script>
|
||||
|
||||
<div class="header">
|
||||
<div class="title"><Label label={chunter.string.Thread} /></div>
|
||||
<div class="tool" on:click={() => { dispatch('close') }}><IconClose size='medium' /></div>
|
||||
</div>
|
||||
<div class="h-full flex-col">
|
||||
<div class="content">
|
||||
{#if message}
|
||||
<div class="flex-col">
|
||||
<MsgView {message} {employees} thread />
|
||||
{#if comments.length}
|
||||
<ChannelSeparator title={chunter.string.RepliesCount} line params={{ replies: message.replies?.length }} />
|
||||
{/if}
|
||||
{#each comments as comment}
|
||||
<ThreadComment {comment} {employees} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex-col vScroll content" bind:this={div}>
|
||||
{#if message}
|
||||
<MsgView {message} {employees} thread />
|
||||
{#if comments.length}
|
||||
<ChannelSeparator title={chunter.string.RepliesCount} line params={{ replies: comments.length }} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ref-input">
|
||||
<AttachmentRefInput {space} _class={chunter.class.Comment} objectId={commentId} on:message={onMessage}/>
|
||||
</div>
|
||||
{#each comments as comment, i (comment._id)}
|
||||
{#if newMessagesPos === i}
|
||||
<ChannelSeparator title={chunter.string.New} line reverse isNew />
|
||||
{/if}
|
||||
<ThreadComment {comment} {employees} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ref-input">
|
||||
<AttachmentRefInput {space} _class={chunter.class.Comment} objectId={commentId} on:message={onMessage}/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@ -134,9 +168,6 @@
|
||||
}
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
margin: 1rem 1rem 0px;
|
||||
padding: 1.5rem 1.5rem 0px;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ export default mergeIds(chunterId, chunter, {
|
||||
Replies: '' as IntlString,
|
||||
Thread: '' as IntlString,
|
||||
RepliesCount: '' as IntlString,
|
||||
LastReply: '' as IntlString
|
||||
LastReply: '' as IntlString,
|
||||
New: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -391,9 +391,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if asideId && navigatorModel?.aside !== undefined}
|
||||
<div class="antiPanel-component indent antiComponent filled">
|
||||
<Component is={navigatorModel.aside} props={{ currentSpace, _id: asideId }} on:close={closeAside} />
|
||||
</div>
|
||||
<div class="antiPanel-component antiComponent border-left"><Component is={navigatorModel.aside} props={{ currentSpace, _id: asideId }} on:close={closeAside} /></div>
|
||||
{/if}
|
||||
</div>
|
||||
<PanelInstance {contentPanel} />
|
||||
|
@ -29,6 +29,7 @@
|
||||
"@anticrm/core": "~0.6.16",
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
"@anticrm/server-core": "~0.6.1",
|
||||
"@anticrm/contact": "~0.6.5",
|
||||
"@anticrm/chunter": "~0.6.1",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/login": "~0.6.1",
|
||||
|
@ -13,10 +13,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import chunter, { Comment, Channel } from '@anticrm/chunter'
|
||||
import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@anticrm/core'
|
||||
import chunter, { Channel, Comment, Message } from '@anticrm/chunter'
|
||||
import { EmployeeAccount } from '@anticrm/contact'
|
||||
import core, { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from '@anticrm/core'
|
||||
import login from '@anticrm/login'
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
import { TriggerControl } from '@anticrm/server-core'
|
||||
import workbench from '@anticrm/workbench'
|
||||
|
||||
/**
|
||||
@ -49,8 +51,40 @@ export async function CommentRemove (doc: Doc, hiearachy: Hierarchy, findAll: <T
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function CommentCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
if (tx._class !== core.class.TxCreateDoc) return []
|
||||
const doc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<Doc>)
|
||||
if (!hierarchy.isDerived(doc._class, chunter.class.Comment)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const comment = doc as Comment
|
||||
if (!hierarchy.isDerived(comment.attachedToClass, chunter.class.Message)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const lastReplyTx = control.txFactory.createTxUpdateDoc<Message>(chunter.class.Message, comment.space, comment.attachedTo as Ref<Message>, {
|
||||
lastReply: tx.modifiedOn
|
||||
})
|
||||
const employee = control.modelDb.getObject(tx.modifiedBy) as EmployeeAccount
|
||||
const employeeTx = control.txFactory.createTxUpdateDoc<Message>(chunter.class.Message, comment.space, comment.attachedTo as Ref<Message>, {
|
||||
$push: { replies: employee.employee }
|
||||
})
|
||||
const result: TxUpdateDoc<Message>[] = []
|
||||
result.push(lastReplyTx)
|
||||
result.push(employeeTx)
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
CommentCreate
|
||||
},
|
||||
function: {
|
||||
CommentRemove,
|
||||
ChannelHTMLPresenter: channelHTMLPresenter,
|
||||
|
@ -16,6 +16,7 @@
|
||||
import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } from '@anticrm/core'
|
||||
import type { Plugin, Resource } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import { TriggerFunc } from '@anticrm/server-core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -26,6 +27,9 @@ export const serverChunterId = 'server-chunter' as Plugin
|
||||
* @public
|
||||
*/
|
||||
export default plugin(serverChunterId, {
|
||||
trigger: {
|
||||
CommentCreate: '' as Resource<TriggerFunc>
|
||||
},
|
||||
function: {
|
||||
CommentRemove: '' as Resource<(doc: Doc, hiearachy: Hierarchy, findAll: <T extends Doc> (clazz: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>) => Promise<FindResult<T>>) => Promise<Doc[]>>,
|
||||
ChannelHTMLPresenter: '' as Resource<(doc: Doc) => string>,
|
||||
|
Loading…
Reference in New Issue
Block a user