mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Migrate thread messages (#1284)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
68774ff1ab
commit
83b28a61e6
@ -14,16 +14,16 @@
|
||||
//
|
||||
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Backlink, Channel, Comment, Message } from '@anticrm/chunter'
|
||||
import type { Account, Class, Doc, Domain, Ref, Timestamp } from '@anticrm/core'
|
||||
import type { Backlink, Channel, ChunterMessage, Comment, Message, ThreadMessage } from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { IndexKind } from '@anticrm/core'
|
||||
import { ArrOf, Builder, Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeTimestamp, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import core, { TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import core, { TAttachedDoc, TSpace } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import chunter from './plugin'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import notification from '@anticrm/model-notification'
|
||||
|
||||
export const DOMAIN_CHUNTER = 'chunter' as Domain
|
||||
@ -36,8 +36,8 @@ export class TChannel extends TSpace implements Channel {
|
||||
lastMessage?: Timestamp
|
||||
}
|
||||
|
||||
@Model(chunter.class.Message, core.class.Doc, DOMAIN_CHUNTER)
|
||||
export class TMessage extends TDoc implements Message {
|
||||
@Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER)
|
||||
export class TChunterMessage extends TAttachedDoc implements ChunterMessage {
|
||||
@Prop(TypeMarkup(), chunter.string.Content)
|
||||
@Index(IndexKind.FullText)
|
||||
content!: string
|
||||
@ -45,12 +45,6 @@ export class TMessage extends TDoc implements Message {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
|
||||
attachments?: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(contact.class.Employee)), chunter.string.Replies)
|
||||
replies?: Ref<Employee>[]
|
||||
|
||||
@Prop(TypeTimestamp(), chunter.string.LastReply)
|
||||
lastReply?: Timestamp
|
||||
|
||||
@Prop(TypeRef(core.class.Account), chunter.string.CreateBy)
|
||||
createBy!: Ref<Account>
|
||||
|
||||
@ -58,6 +52,26 @@ export class TMessage extends TDoc implements Message {
|
||||
createOn!: Timestamp
|
||||
}
|
||||
|
||||
@Model(chunter.class.ThreadMessage, chunter.class.ChunterMessage)
|
||||
export class TThreadMessage extends TChunterMessage implements ThreadMessage {
|
||||
declare attachedTo: Ref<Message>
|
||||
|
||||
declare attachedToClass: Ref<Class<Message>>
|
||||
}
|
||||
|
||||
@Model(chunter.class.Message, chunter.class.ChunterMessage)
|
||||
export class TMessage extends TChunterMessage implements Message {
|
||||
declare attachedTo: Ref<Space>
|
||||
|
||||
declare attachedToClass: Ref<Class<Space>>
|
||||
|
||||
@Prop(ArrOf(TypeRef(contact.class.Employee)), chunter.string.Replies)
|
||||
replies?: Ref<Employee>[]
|
||||
|
||||
@Prop(TypeTimestamp(), chunter.string.LastReply)
|
||||
lastReply?: Timestamp
|
||||
}
|
||||
|
||||
@Model(chunter.class.Comment, core.class.AttachedDoc, DOMAIN_COMMENT)
|
||||
@UX(chunter.string.Comment)
|
||||
export class TComment extends TAttachedDoc implements Comment {
|
||||
@ -77,7 +91,7 @@ export class TBacklink extends TComment implements Backlink {
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TChannel, TMessage, TComment, TBacklink)
|
||||
builder.createModel(TChannel, TMessage, TThreadMessage, TChunterMessage, TComment, TBacklink)
|
||||
builder.mixin(chunter.class.Channel, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: chunter.class.Message
|
||||
@ -130,11 +144,8 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: chunter.class.Comment,
|
||||
action: chunter.action.MarkCommentUnread,
|
||||
query: {
|
||||
attachedToClass: chunter.class.Message
|
||||
}
|
||||
target: chunter.class.ThreadMessage,
|
||||
action: chunter.action.MarkCommentUnread
|
||||
})
|
||||
|
||||
builder.createDoc(workbench.class.Application, core.space.Model, {
|
||||
|
@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Message } from '@anticrm/chunter'
|
||||
import type { Client, Ref } from '@anticrm/core'
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import { Comment, Message, ThreadMessage } from '@anticrm/chunter'
|
||||
import core, { Client, Doc, DOMAIN_TX, Ref, TxCreateDoc, TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import { DOMAIN_CHUNTER, DOMAIN_COMMENT } from './index'
|
||||
import chunter from './plugin'
|
||||
|
||||
export async function createDeps (client: Client): Promise<void> {
|
||||
@ -69,8 +69,80 @@ export async function setCreate (client: TxOperations): Promise<void> {
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
export async function migrateMessages (client: MigrationClient): Promise<void> {
|
||||
const messages = await client.find(DOMAIN_CHUNTER, {
|
||||
_class: chunter.class.Message,
|
||||
attachedTo: { $exists: false }
|
||||
})
|
||||
for (const message of messages) {
|
||||
await client.update(DOMAIN_CHUNTER, {
|
||||
_id: message._id
|
||||
}, {
|
||||
attachedTo: message.space,
|
||||
attachedToClass: chunter.class.Channel,
|
||||
collection: 'messages'
|
||||
})
|
||||
}
|
||||
|
||||
const txes = await client.find<TxCreateDoc<Doc>>(DOMAIN_TX, {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: chunter.class.Message
|
||||
})
|
||||
for (const tx of txes) {
|
||||
await client.update(DOMAIN_TX, {
|
||||
_id: tx._id
|
||||
}, {
|
||||
'attributes.attachedTo': tx.objectSpace,
|
||||
'attributes.attachedToClass': chunter.class.Channel,
|
||||
'attributes.collection': 'messages'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateThreadMessages (client: MigrationClient): Promise<void> {
|
||||
const messages = await client.find<Comment>(DOMAIN_COMMENT, {
|
||||
_class: chunter.class.Comment,
|
||||
attachedToClass: chunter.class.Message
|
||||
})
|
||||
for (const message of messages) {
|
||||
await client.delete(DOMAIN_COMMENT, message._id)
|
||||
await client.create<ThreadMessage>(DOMAIN_CHUNTER, {
|
||||
attachedTo: message.attachedTo as Ref<Message>,
|
||||
attachedToClass: message.attachedToClass,
|
||||
attachments: message.attachments,
|
||||
content: message.message,
|
||||
collection: message.collection,
|
||||
_class: chunter.class.ThreadMessage,
|
||||
space: message.space,
|
||||
modifiedOn: message.modifiedOn,
|
||||
modifiedBy: message.modifiedBy,
|
||||
createBy: message.modifiedBy,
|
||||
createOn: message.modifiedOn,
|
||||
_id: message._id as string as Ref<ThreadMessage>
|
||||
})
|
||||
}
|
||||
|
||||
const txes = await client.find<TxCreateDoc<Comment>>(DOMAIN_TX, {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: chunter.class.Comment,
|
||||
'attributes.attachedToClass': chunter.class.Message
|
||||
})
|
||||
for (const tx of txes) {
|
||||
await client.update(DOMAIN_TX, {
|
||||
_id: tx._id
|
||||
}, {
|
||||
objectClass: chunter.class.ThreadMessage,
|
||||
'attributes.createBy': tx.modifiedBy,
|
||||
'attributes.createOn': tx.modifiedOn,
|
||||
'attributes.content': tx.attributes.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const chunterOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await migrateMessages(client)
|
||||
await migrateThreadMessages(client)
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
|
@ -97,7 +97,7 @@
|
||||
function markUnread (lastViews: Map<Ref<Doc>, number>) {
|
||||
if (messages === undefined) return
|
||||
const newPos = newMessagesStart(messages)
|
||||
if (newPos < newMessagesPos || newMessagesPos === -1) {
|
||||
if (newPos !== -1 || newMessagesPos === -1) {
|
||||
newMessagesPos = newPos
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,9 @@
|
||||
const me = getCurrentAccount()._id
|
||||
const txFactory = new TxFactory(me)
|
||||
const tx = txFactory.createTxCreateDoc<Message>(_class, space, {
|
||||
attachedTo: space,
|
||||
attachedToClass: chunter.class.Channel,
|
||||
collection: 'messages',
|
||||
content: message,
|
||||
createOn: 0,
|
||||
createBy: me,
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Attachment } from '@anticrm/attachment'
|
||||
import { AttachmentList } from '@anticrm/attachment-resources'
|
||||
import type { Comment } from '@anticrm/chunter'
|
||||
import type { ThreadMessage } from '@anticrm/chunter'
|
||||
import { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { Ref, WithLookup } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
@ -31,10 +31,10 @@
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import Reactions from './Reactions.svelte'
|
||||
|
||||
export let comment: WithLookup<Comment>
|
||||
export let message: WithLookup<ThreadMessage>
|
||||
export let employees: Map<Ref<Employee>, Employee>
|
||||
|
||||
$: attachments = (comment.$lookup?.attachments ?? []) as Attachment[]
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
$: subscribed = ($lastViews.get(comment.attachedTo) ?? -1) > -1
|
||||
$: subscribed = ($lastViews.get(message.attachedTo) ?? -1) > -1
|
||||
$: subscribeAction = subscribed
|
||||
? ({
|
||||
label: chunter.string.TurnOffReplies,
|
||||
@ -54,7 +54,7 @@
|
||||
} as Action)
|
||||
|
||||
const showMenu = async (ev: Event): Promise<void> => {
|
||||
const actions = await getActions(client, comment, chunter.class.Comment)
|
||||
const actions = await getActions(client, message, chunter.class.ThreadMessage)
|
||||
actions.push(subscribeAction)
|
||||
showPopup(
|
||||
Menu,
|
||||
@ -65,7 +65,7 @@
|
||||
icon: a.icon,
|
||||
action: async () => {
|
||||
const impl = await getResource(a.action)
|
||||
await impl(comment)
|
||||
await impl(message)
|
||||
}
|
||||
}))
|
||||
]
|
||||
@ -74,10 +74,10 @@
|
||||
)
|
||||
}
|
||||
|
||||
$: employee = getEmployee(comment)
|
||||
$: employee = getEmployee(message)
|
||||
|
||||
function getEmployee (comment: WithLookup<Comment>): Employee | undefined {
|
||||
const employee = (comment.$lookup?.modifiedBy as EmployeeAccount)?.employee
|
||||
function getEmployee (comment: WithLookup<ThreadMessage>): Employee | undefined {
|
||||
const employee = (comment.$lookup?.createBy as EmployeeAccount)?.employee
|
||||
if (employee !== undefined) {
|
||||
return employees.get(employee)
|
||||
}
|
||||
@ -89,10 +89,10 @@
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
{#if employee}{formatName(employee.name)}{/if}
|
||||
<span>{getTime(comment.modifiedOn)}</span>
|
||||
<span>{getTime(message.createOn)}</span>
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={comment.message} /></div>
|
||||
{#if comment.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
|
||||
<div class="text"><MessageViewer message={message.content} /></div>
|
||||
{#if message.attachments}<div class="attachments"><AttachmentList {attachments} /></div>{/if}
|
||||
{#if reactions}
|
||||
<div class="footer">
|
||||
<div><Reactions /></div>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import type { Comment, Message } from '@anticrm/chunter'
|
||||
import type { ThreadMessage, Message } from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import core, { Doc, generateId, getCurrentAccount, Ref, Space, TxFactory } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
@ -34,9 +34,9 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let _id: Ref<Message>
|
||||
export let space: Ref<Space>
|
||||
export let currentSpace: Ref<Space>
|
||||
let message: Message | undefined
|
||||
let commentId = generateId()
|
||||
let commentId = generateId() as Ref<ThreadMessage>
|
||||
|
||||
let div: HTMLDivElement | undefined
|
||||
let autoscroll: boolean = false
|
||||
@ -54,7 +54,7 @@
|
||||
|
||||
const lookup = {
|
||||
_id: { attachments: attachment.class.Attachment },
|
||||
modifiedBy: core.class.Account
|
||||
createBy: core.class.Account
|
||||
}
|
||||
|
||||
$: updateQueries(_id)
|
||||
@ -75,7 +75,7 @@
|
||||
)
|
||||
|
||||
query.query(
|
||||
chunter.class.Comment,
|
||||
chunter.class.ThreadMessage,
|
||||
{
|
||||
attachedTo: id
|
||||
},
|
||||
@ -108,35 +108,37 @@
|
||||
const { message, attachments } = event.detail
|
||||
const me = getCurrentAccount()._id
|
||||
const txFactory = new TxFactory(me)
|
||||
const tx = txFactory.createTxCreateDoc(
|
||||
chunter.class.Comment,
|
||||
space,
|
||||
const tx = txFactory.createTxCreateDoc<ThreadMessage>(
|
||||
chunter.class.ThreadMessage,
|
||||
currentSpace,
|
||||
{
|
||||
attachedTo: _id,
|
||||
attachedToClass: chunter.class.Message,
|
||||
collection: 'replies',
|
||||
message,
|
||||
content: message,
|
||||
createBy: me,
|
||||
createOn: 0,
|
||||
attachments
|
||||
},
|
||||
commentId
|
||||
)
|
||||
|
||||
tx.attributes.createOn = tx.modifiedOn
|
||||
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)
|
||||
await createBacklinks(client, currentSpace, chunter.class.Channel, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
}
|
||||
let comments: Comment[] = []
|
||||
let comments: ThreadMessage[] = []
|
||||
|
||||
function newMessagesStart (comments: Comment[]): number {
|
||||
function newMessagesStart (comments: ThreadMessage[]): number {
|
||||
const lastView = $lastViews.get(_id)
|
||||
if (lastView === undefined || lastView === -1) return -1
|
||||
for (let index = 0; index < comments.length; index++) {
|
||||
const comment = comments[index]
|
||||
if (comment.modifiedOn > lastView) return index
|
||||
if (comment.createOn > lastView) return index
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@ -144,7 +146,7 @@
|
||||
$: markUnread($lastViews)
|
||||
function markUnread (lastViews: Map<Ref<Doc>, number>) {
|
||||
const newPos = newMessagesStart(comments)
|
||||
if (newPos < newMessagesPos || newMessagesPos === -1) {
|
||||
if (newPos !== -1 || newMessagesPos === -1) {
|
||||
newMessagesPos = newPos
|
||||
}
|
||||
}
|
||||
@ -172,12 +174,12 @@
|
||||
{#if newMessagesPos === i}
|
||||
<ChannelSeparator title={chunter.string.New} line reverse isNew />
|
||||
{/if}
|
||||
<ThreadComment {comment} {employees} />
|
||||
<ThreadComment message={comment} {employees} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ref-input">
|
||||
<AttachmentRefInput {space} _class={chunter.class.Comment} objectId={commentId} on:message={onMessage} />
|
||||
<AttachmentRefInput space={currentSpace} _class={chunter.class.Comment} objectId={commentId} on:message={onMessage} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import chunter, { Comment, Message } from '@anticrm/chunter'
|
||||
import chunter, { Comment, Message, ThreadMessage } from '@anticrm/chunter'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import TxBacklinkCreate from './components/activity/TxBacklinkCreate.svelte'
|
||||
@ -34,7 +34,7 @@ async function MarkUnread (object: Message): Promise<void> {
|
||||
await client.updateLastView(object.space, chunter.class.Channel, object.createOn - 1, true)
|
||||
}
|
||||
|
||||
async function MarkCommentUnread (object: Comment): Promise<void> {
|
||||
async function MarkCommentUnread (object: ThreadMessage): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
const value = object.modifiedOn - 1
|
||||
await client.updateLastView(object.attachedTo, object.attachedToClass, value, true)
|
||||
@ -45,7 +45,7 @@ async function SubscribeMessage (object: Message): Promise<void> {
|
||||
await client.updateLastView(object._id, object._class, undefined, true)
|
||||
}
|
||||
|
||||
async function SubscribeComment (object: Comment): Promise<void> {
|
||||
async function SubscribeComment (object: ThreadMessage): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
await client.updateLastView(object.attachedTo, object.attachedToClass, undefined, true)
|
||||
}
|
||||
@ -55,7 +55,7 @@ async function UnsubscribeMessage (object: Message): Promise<void> {
|
||||
await client.unsubscribe(object._id)
|
||||
}
|
||||
|
||||
async function UnsubscribeComment (object: Comment): Promise<void> {
|
||||
async function UnsubscribeComment (object: ThreadMessage): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
await client.unsubscribe(object.attachedTo)
|
||||
}
|
||||
|
@ -29,15 +29,31 @@ export interface Channel extends Space {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Message extends Doc {
|
||||
export interface ChunterMessage extends AttachedDoc {
|
||||
content: string
|
||||
attachments?: number
|
||||
replies?: Ref<Employee>[]
|
||||
lastReply?: Timestamp
|
||||
createBy: Ref<Account>
|
||||
createOn: Timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ThreadMessage extends ChunterMessage {
|
||||
attachedTo: Ref<Message>
|
||||
attachedToClass: Ref<Class<Message>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Message extends ChunterMessage {
|
||||
attachedTo: Ref<Space>
|
||||
attachedToClass: Ref<Class<Space>>
|
||||
replies?: Ref<Employee>[]
|
||||
lastReply?: Timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -80,6 +96,8 @@ export default plugin(chunterId, {
|
||||
},
|
||||
class: {
|
||||
Message: '' as Ref<Class<Message>>,
|
||||
ChunterMessage: '' as Ref<Class<ChunterMessage>>,
|
||||
ThreadMessage: '' as Ref<Class<ThreadMessage>>,
|
||||
Backlink: '' as Ref<Class<Backlink>>,
|
||||
Comment: '' as Ref<Class<Comment>>,
|
||||
Channel: '' as Ref<Class<Channel>>
|
||||
|
Loading…
Reference in New Issue
Block a user