mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
[UBER-686] Chat in inbox (v2) (#3583)
Signed-off-by: Oleg Solodkov <oleg.solodkov@xored.com>
This commit is contained in:
parent
f18bc8e700
commit
4c38e920d5
@ -17787,7 +17787,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/chunter.tgz:
|
||||
resolution: {integrity: sha512-P+ZhImPhzQJDSPEIR+zEXNT87Rn3CvGG+7JN1OsF+3tVdNBnlkCQ/qrmK70Xp0tktXzolaB54lITf9luPQn/Tw==, tarball: file:projects/chunter.tgz}
|
||||
resolution: {integrity: sha512-YvadGb0k4jR7JApOhvPTLZKTm3fX68dE9zX4j0GvCI6XrkUzZdD4oTa1IF8uuA3DRpHSt5pjNF8p1mI1ptK+BA==, tarball: file:projects/chunter.tgz}
|
||||
name: '@rush-temp/chunter'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -17800,6 +17800,7 @@ packages:
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
fast-equals: 2.0.4
|
||||
prettier: 2.8.8
|
||||
typescript: 4.8.4
|
||||
transitivePeerDependencies:
|
||||
@ -19106,7 +19107,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-notification.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-vZSXHfSkfeklcsrLofIc/OuwLLUj6gT4o9Jl91MgLWeXjiIzaqZLBN6EcGegQ7nu9DU+8tWv0iZcWR6bpqWqcQ==, tarball: file:projects/model-notification.tgz}
|
||||
resolution: {integrity: sha512-4TdxvQk1W0Q+g6eF8APHXTVp+ZZFHo2/HjnZZ7nYYqbEJy4EJUGvcCRXhbjsPqAgmoC80Y+D/GVdttR4fJKdnA==, tarball: file:projects/model-notification.tgz}
|
||||
id: file:projects/model-notification.tgz
|
||||
name: '@rush-temp/model-notification'
|
||||
version: 0.0.0
|
||||
@ -19928,7 +19929,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/notification-resources.tgz_a1d864769aaf53d09b76fe134ab55e60:
|
||||
resolution: {integrity: sha512-q3n0DYJDx1EejE47b3uDDVn9cKjcqNCkm502u0+TR6C3owM0rWljB9s0PGAZmJ66VWtglB0LMszUhJ1IqX4n0w==, tarball: file:projects/notification-resources.tgz}
|
||||
resolution: {integrity: sha512-m2wYJypLkf9adSBx5NAW/1RJ7soDNp/VJb6WJk8vfdTfsIK9ttrm321UGN5FoWg3DP6H/UvCZObBN344cKno/w==, tarball: file:projects/notification-resources.tgz}
|
||||
id: file:projects/notification-resources.tgz
|
||||
name: '@rush-temp/notification-resources'
|
||||
version: 0.0.0
|
||||
@ -19941,6 +19942,7 @@ packages:
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
eslint-plugin-svelte3: 4.0.0_eslint@8.27.0+svelte@3.55.1
|
||||
fast-equals: 2.0.4
|
||||
prettier: 2.8.8
|
||||
prettier-plugin-svelte: 2.8.0_prettier@2.8.8+svelte@3.55.1
|
||||
sass: 1.56.1
|
||||
|
@ -25,7 +25,8 @@ import {
|
||||
Message,
|
||||
Reaction,
|
||||
SavedMessages,
|
||||
ThreadMessage
|
||||
ThreadMessage,
|
||||
DirectMessageInput
|
||||
} from '@hcengineering/chunter'
|
||||
import contact, { Person } from '@hcengineering/contact'
|
||||
import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@hcengineering/core'
|
||||
@ -35,6 +36,7 @@ import {
|
||||
Builder,
|
||||
Collection,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
@ -45,12 +47,13 @@ import {
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import core, { TAttachedDoc, TSpace } from '@hcengineering/model-core'
|
||||
import core, { TAttachedDoc, TClass, TSpace } from '@hcengineering/model-core'
|
||||
import notification from '@hcengineering/model-notification'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import view, { createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import chunter from './plugin'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
export { chunterId } from '@hcengineering/chunter'
|
||||
export { chunterOperation } from './migration'
|
||||
|
||||
@ -158,6 +161,11 @@ export class TSavedMessages extends TPreference implements SavedMessages {
|
||||
attachedTo!: Ref<ChunterMessage>
|
||||
}
|
||||
|
||||
@Mixin(chunter.mixin.DirectMessageInput, core.class.Class)
|
||||
export class TDirectMessageInput extends TClass implements DirectMessageInput {
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder, options = { addApplication: true }): void {
|
||||
builder.createModel(
|
||||
TChunterSpace,
|
||||
@ -169,7 +177,8 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
TBacklink,
|
||||
TDirectMessage,
|
||||
TSavedMessages,
|
||||
TReaction
|
||||
TReaction,
|
||||
TDirectMessageInput
|
||||
)
|
||||
const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage]
|
||||
|
||||
@ -213,6 +222,14 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
presenter: chunter.component.DmPresenter
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.NotificationPreview, {
|
||||
presenter: chunter.component.ChannelPreview
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.DirectMessage, core.class.Class, chunter.mixin.DirectMessageInput, {
|
||||
component: chunter.component.DirectMessageInput
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.NotificationObjectPresenter, {
|
||||
presenter: chunter.component.ThreadParentPresenter
|
||||
})
|
||||
|
@ -31,7 +31,6 @@ export default mergeIds(chunterId, chunter, {
|
||||
MessagePresenter: '' as AnyComponent,
|
||||
DmPresenter: '' as AnyComponent,
|
||||
Threads: '' as AnyComponent,
|
||||
ThreadView: '' as AnyComponent,
|
||||
SavedMessages: '' as AnyComponent,
|
||||
ChunterBrowser: '' as AnyComponent
|
||||
},
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@hcengineering/workbench": "^0.6.8",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
"@hcengineering/setting": "^0.6.9"
|
||||
"@hcengineering/setting": "^0.6.9",
|
||||
"@hcengineering/chunter": "^0.6.10"
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import {
|
||||
NotificationGroup,
|
||||
notificationId,
|
||||
NotificationObjectPresenter,
|
||||
NotificationPreview,
|
||||
NotificationProvider,
|
||||
NotificationSetting,
|
||||
NotificationStatus,
|
||||
@ -54,7 +55,7 @@ import setting from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import notification from './plugin'
|
||||
import activity from '@hcengineering/activity'
|
||||
|
||||
import chunter from '@hcengineering/chunter'
|
||||
export { notificationId } from '@hcengineering/notification'
|
||||
export { notificationOperation } from './migration'
|
||||
export { notification as default }
|
||||
@ -148,6 +149,11 @@ export class TNotificationObjectPresenter extends TClass implements Notification
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Mixin(notification.mixin.NotificationPreview, core.class.Class)
|
||||
export class TNotificationPreview extends TClass implements NotificationPreview {
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(notification.class.DocUpdates, core.class.Doc, DOMAIN_NOTIFICATION)
|
||||
export class TDocUpdates extends TDoc implements DocUpdates {
|
||||
@Index(IndexKind.Indexed)
|
||||
@ -175,7 +181,8 @@ export function createModel (builder: Builder): void {
|
||||
TClassCollaborators,
|
||||
TCollaborators,
|
||||
TDocUpdates,
|
||||
TNotificationObjectPresenter
|
||||
TNotificationObjectPresenter,
|
||||
TNotificationPreview
|
||||
)
|
||||
|
||||
// Temporarily disabled, we should think about it
|
||||
@ -230,7 +237,8 @@ export function createModel (builder: Builder): void {
|
||||
icon: notification.icon.Notifications,
|
||||
alias: notificationId,
|
||||
hidden: true,
|
||||
component: notification.component.Inbox
|
||||
component: notification.component.Inbox,
|
||||
aside: chunter.component.ThreadView
|
||||
},
|
||||
notification.app.Notification
|
||||
)
|
||||
@ -344,6 +352,21 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
notification.ids.TxCollaboratorsChange
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.DirectMessage,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxCreateDoc,
|
||||
component: notification.activity.TxDmCreation,
|
||||
display: 'inline',
|
||||
editable: false,
|
||||
hideOnRemove: true
|
||||
},
|
||||
notification.ids.TxDmCreation
|
||||
)
|
||||
}
|
||||
|
||||
export function generateClassNotificationTypes (
|
||||
|
@ -35,10 +35,12 @@ export default mergeIds(notificationId, notification, {
|
||||
Notification: '' as Ref<Application>
|
||||
},
|
||||
activity: {
|
||||
TxCollaboratorsChange: '' as AnyComponent
|
||||
TxCollaboratorsChange: '' as AnyComponent,
|
||||
TxDmCreation: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
TxCollaboratorsChange: '' as Ref<TxViewlet>
|
||||
TxCollaboratorsChange: '' as Ref<TxViewlet>,
|
||||
TxDmCreation: '' as Ref<TxViewlet>
|
||||
},
|
||||
component: {
|
||||
NotificationSettings: '' as AnyComponent
|
||||
|
@ -45,6 +45,14 @@ export function createModel (builder: Builder): void {
|
||||
trigger: serverChunter.trigger.ChunterTrigger
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverChunter.trigger.OnDmCreate,
|
||||
txMatch: {
|
||||
objectClass: chunter.class.DirectMessage,
|
||||
_class: core.class.TxCreateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(chunter.ids.DMNotification, notification.class.NotificationType, serverNotification.mixin.TypeMatch, {
|
||||
func: serverChunter.function.IsDirectMessage
|
||||
})
|
||||
|
@ -71,6 +71,8 @@
|
||||
"DMNotification": "Sent you a message",
|
||||
"ConfigLabel": "Chat",
|
||||
"ConfigDescription": "Extension to perform text communications",
|
||||
"LastMessage": "Last message"
|
||||
"LastMessage": "Last message",
|
||||
"You": "You",
|
||||
"YouHaveStartedAConversation": "You have started a conversation"
|
||||
}
|
||||
}
|
@ -71,6 +71,8 @@
|
||||
"DMNotification": "Отправил сообщение",
|
||||
"ConfigLabel": "Чат",
|
||||
"ConfigDescription": "Расширение для текстовых переписок",
|
||||
"LastMessage": "Последнее сообщение"
|
||||
"LastMessage": "Последнее сообщение",
|
||||
"You": "Вы",
|
||||
"YouHaveStartedAConversation": "Вы начали диалог"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Backlink } from '@hcengineering/chunter'
|
||||
import { Backlink, getBacklinks } from '@hcengineering/chunter'
|
||||
import contact, { PersonAccount } from '@hcengineering/contact'
|
||||
import { Account, Class, Client, Data, Doc, DocumentQuery, Ref, TxOperations } from '@hcengineering/core'
|
||||
import chunter from './plugin'
|
||||
@ -33,81 +33,6 @@ export function isToday (time: number): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function extractBacklinks (
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
message: string,
|
||||
kids: NodeListOf<ChildNode>
|
||||
): Array<Data<Backlink>> {
|
||||
const result: Array<Data<Backlink>> = []
|
||||
|
||||
const nodes: Array<NodeListOf<ChildNode>> = [kids]
|
||||
while (true) {
|
||||
const nds = nodes.shift()
|
||||
if (nds === undefined) {
|
||||
break
|
||||
}
|
||||
nds.forEach((kid) => {
|
||||
if (
|
||||
kid.nodeType === Node.ELEMENT_NODE &&
|
||||
(kid as HTMLElement).localName === 'span' &&
|
||||
(kid as HTMLElement).getAttribute('data-type') === 'reference'
|
||||
) {
|
||||
const el = kid as HTMLElement
|
||||
const ato = el.getAttribute('data-id') as Ref<Doc>
|
||||
const atoClass = el.getAttribute('data-objectclass') as Ref<Class<Doc>>
|
||||
const e = result.find((e) => e.attachedTo === ato && e.attachedToClass === atoClass)
|
||||
if (e === undefined && ato !== attachedDocId && ato !== backlinkId) {
|
||||
result.push({
|
||||
attachedTo: ato,
|
||||
attachedToClass: atoClass,
|
||||
collection: 'backlinks',
|
||||
backlinkId,
|
||||
backlinkClass,
|
||||
message: el.parentElement?.innerHTML ?? '',
|
||||
attachedDocId
|
||||
})
|
||||
}
|
||||
}
|
||||
nodes.push(kid.childNodes)
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function getBacklinks (
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
content: string
|
||||
): Array<Data<Backlink>> {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, 'text/html')
|
||||
return extractBacklinks(backlinkId, backlinkClass, attachedDocId, content, doc.childNodes as NodeListOf<HTMLElement>)
|
||||
}
|
||||
|
||||
export async function createBacklinks (
|
||||
client: TxOperations,
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
||||
for (const backlink of backlinks) {
|
||||
const { attachedTo, attachedToClass, collection, ...adata } = backlink
|
||||
await client.addCollection(
|
||||
chunter.class.Backlink,
|
||||
chunter.space.Backlinks,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
collection,
|
||||
adata
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -0,0 +1,69 @@
|
||||
<!--
|
||||
// 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 { SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import chunter, { ChunterMessage, DirectMessage } from '@hcengineering/chunter'
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
import chunterResources from '../plugin'
|
||||
import MessagePreview from './MessagePreview.svelte'
|
||||
|
||||
export let object: DirectMessage
|
||||
export let newTxes: number
|
||||
|
||||
const NUM_OF_RECENT_MESSAGES = 5 as const
|
||||
|
||||
let messages: ChunterMessage[] = []
|
||||
const messagesQuery = createQuery()
|
||||
$: messagesQuery.query(
|
||||
chunter.class.ChunterMessage,
|
||||
{ attachedTo: object._id },
|
||||
(res) => {
|
||||
if (res !== undefined) {
|
||||
messages = res.sort((a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0))
|
||||
}
|
||||
},
|
||||
{
|
||||
limit: newTxes + NUM_OF_RECENT_MESSAGES,
|
||||
sort: {
|
||||
createdOn: SortingOrder.Descending
|
||||
},
|
||||
lookup: {
|
||||
_id: { attachments: attachment.class.Attachment }
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="flex-col flex-gap-3 preview-container">
|
||||
{#if messages.length}
|
||||
{#each messages as message}
|
||||
<MessagePreview value={message} />
|
||||
{/each}
|
||||
{:else}
|
||||
<Label label={chunterResources.string.YouHaveStartedAConversation} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.preview-container {
|
||||
padding: 0.5rem;
|
||||
background-color: var(--theme-bg-color);
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
</style>
|
@ -15,11 +15,10 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { ChunterMessage, ChunterSpace, Message } from '@hcengineering/chunter'
|
||||
import { ChunterMessage, ChunterSpace, Message, createBacklinks } from '@hcengineering/chunter'
|
||||
import { Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getLocation, navigate } from '@hcengineering/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
import Channel from './Channel.svelte'
|
||||
import PinnedMessages from './PinnedMessages.svelte'
|
||||
|
@ -15,10 +15,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { Comment } from '@hcengineering/chunter'
|
||||
import { Comment, createBacklinks } from '@hcengineering/chunter'
|
||||
import { AttachedData, Doc, generateId, Ref } from '@hcengineering/core'
|
||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let object: Doc
|
||||
|
@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import chunter, { DirectMessage, Message, createBacklinks, getDirectChannel } from '@hcengineering/chunter'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
export let account: PersonAccount
|
||||
export let loading: boolean = true
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
const _class = chunter.class.Message
|
||||
let messageId = generateId() as Ref<Message>
|
||||
|
||||
let space: Ref<DirectMessage> | undefined
|
||||
|
||||
$: _getDirectChannel(account?._id)
|
||||
async function _getDirectChannel (account?: Ref<PersonAccount>): Promise<void> {
|
||||
if (account === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
space = await getDirectChannel(client, me as Ref<PersonAccount>, account)
|
||||
}
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
if (space === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { message, attachments } = event.detail
|
||||
await client.addCollection(
|
||||
_class,
|
||||
space,
|
||||
space,
|
||||
chunter.class.DirectMessage,
|
||||
'messages',
|
||||
{
|
||||
content: message,
|
||||
createBy: me,
|
||||
attachments
|
||||
},
|
||||
messageId
|
||||
)
|
||||
|
||||
await createBacklinks(client, space, chunter.class.ChunterSpace, messageId, message)
|
||||
|
||||
messageId = generateId()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if space !== undefined}
|
||||
<div class="reference">
|
||||
<AttachmentRefInput bind:loading {space} {_class} objectId={messageId} on:message={onMessage} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.reference {
|
||||
margin: 1.25rem 2.5rem;
|
||||
}
|
||||
</style>
|
@ -28,7 +28,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { AddMessageToSaved, DeleteMessageFromSaved, UnpinMessage } from '../index'
|
||||
import chunter from '../plugin'
|
||||
import { getTime } from '../utils'
|
||||
import { getLinks, getTime } from '../utils'
|
||||
// import Share from './icons/Share.svelte'
|
||||
import notification, { Collaborators } from '@hcengineering/notification'
|
||||
import Bookmark from './icons/Bookmark.svelte'
|
||||
@ -200,24 +200,6 @@
|
||||
|
||||
$: links = getLinks(message.content)
|
||||
|
||||
function getLinks (content: string): HTMLLinkElement[] {
|
||||
const parser = new DOMParser()
|
||||
const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement
|
||||
return parseLinks(parent.childNodes)
|
||||
}
|
||||
|
||||
function parseLinks (nodes: NodeListOf<ChildNode>): HTMLLinkElement[] {
|
||||
const res: HTMLLinkElement[] = []
|
||||
nodes.forEach((p) => {
|
||||
if (p.nodeType !== Node.TEXT_NODE) {
|
||||
if (p.nodeName === 'A') {
|
||||
res.push(p as HTMLLinkElement)
|
||||
}
|
||||
res.push(...parseLinks(p.childNodes))
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
let loading = false
|
||||
</script>
|
||||
|
||||
|
119
plugins/chunter-resources/src/components/MessagePreview.svelte
Normal file
119
plugins/chunter-resources/src/components/MessagePreview.svelte
Normal file
@ -0,0 +1,119 @@
|
||||
<!--
|
||||
// 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 { ChunterMessage } from '@hcengineering/chunter'
|
||||
import { MessageViewer } from '@hcengineering/presentation'
|
||||
import ui, { Label, tooltip } from '@hcengineering/ui'
|
||||
import { LinkPresenter } from '@hcengineering/view-resources'
|
||||
import { AttachmentList } from '@hcengineering/attachment-resources'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Ref, WithLookup, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { EmployeePresenter, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
|
||||
import chunter from '../plugin'
|
||||
import { getLinks, getTime } from '../utils'
|
||||
|
||||
export let value: WithLookup<ChunterMessage>
|
||||
|
||||
$: attachments = (value.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
$: links = getLinks(value.content)
|
||||
|
||||
const me = getCurrentAccount()._id as Ref<PersonAccount>
|
||||
|
||||
let account: PersonAccount | undefined
|
||||
|
||||
$: account = $personAccountByIdStore.get(value.createdBy as Ref<PersonAccount>)
|
||||
$: employee = account && $personByIdStore.get(account.person)
|
||||
</script>
|
||||
|
||||
<div class="container clear-mins" class:highlighted={false} id={value._id}>
|
||||
<div class="message clear-mins">
|
||||
<div class="flex-row-center header clear-mins">
|
||||
{#if employee && account}
|
||||
{#if account._id !== me}
|
||||
<EmployeePresenter value={employee} shouldShowAvatar={true} disabled />
|
||||
{:else}
|
||||
<Label label={chunter.string.You} />
|
||||
{/if}
|
||||
{/if}
|
||||
<span>{getTime(value.createdOn ?? 0)}</span>
|
||||
{#if value.editedOn}
|
||||
<span use:tooltip={{ label: ui.string.TimeTooltip, props: { value: getTime(value.editedOn) } }}>
|
||||
<Label label={getEmbeddedLabel('Edited')} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text"><MessageViewer message={value.content} /></div>
|
||||
{#if value.attachments}
|
||||
<div class="attachments">
|
||||
<AttachmentList {attachments} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each links as link}
|
||||
<LinkPresenter {link} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@keyframes highlight {
|
||||
50% {
|
||||
background-color: var(--theme-warning-color);
|
||||
}
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding: 0.5rem 0.15rem;
|
||||
|
||||
&.highlighted {
|
||||
animation: highlight 2000ms ease-in-out;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-left: 1rem;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
line-height: 150%;
|
||||
color: var(--theme-caption-color);
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
font-weight: 400;
|
||||
|
||||
line-height: 1.125rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
line-height: 150%;
|
||||
user-select: contain;
|
||||
}
|
||||
.attachments {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -15,14 +15,14 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import type { ChunterSpace, Message, ThreadMessage } from '@hcengineering/chunter'
|
||||
import { createBacklinks, type ChunterSpace, type Message, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import contact, { Person, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||
import core, { FindOptions, IdMap, Ref, SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
|
||||
import chunter from '../plugin'
|
||||
import ChannelPresenter from './ChannelPresenter.svelte'
|
||||
import DmPresenter from './DmPresenter.svelte'
|
||||
|
@ -15,14 +15,14 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import type { ChunterMessage, Message, ThreadMessage } from '@hcengineering/chunter'
|
||||
import { createBacklinks, type ChunterMessage, type Message, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import core, { Doc, Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { DocUpdates } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { IconClose, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
|
||||
import { afterUpdate, beforeUpdate, createEventDispatcher } from 'svelte'
|
||||
import { createBacklinks } from '../backlinks'
|
||||
|
||||
import chunter from '../plugin'
|
||||
import { isMessageHighlighted, messageIdForScroll, scrollAndHighLight, shouldScrollToMessage } from '../utils'
|
||||
import ChannelSeparator from './ChannelSeparator.svelte'
|
||||
|
@ -44,8 +44,11 @@ import CreateDirectMessage from './components/CreateDirectMessage.svelte'
|
||||
import DirectMessagePresenter from './components/DirectMessagePresenter.svelte'
|
||||
import DmHeader from './components/DmHeader.svelte'
|
||||
import DmPresenter from './components/DmPresenter.svelte'
|
||||
import DirectMessageInput from './components/DirectMessageInput.svelte'
|
||||
import EditChannel from './components/EditChannel.svelte'
|
||||
import MessagePresenter from './components/MessagePresenter.svelte'
|
||||
import ChannelPreview from './components/ChannelPreview.svelte'
|
||||
import MessagePreview from './components/MessagePreview.svelte'
|
||||
import SavedMessages from './components/SavedMessages.svelte'
|
||||
import ThreadParentPresenter from './components/ThreadParentPresenter.svelte'
|
||||
import ThreadView from './components/ThreadView.svelte'
|
||||
@ -64,7 +67,7 @@ import { getDmName, getLink, getTitle, resolveLocation } from './utils'
|
||||
export { default as Header } from './components/Header.svelte'
|
||||
export { classIcon } from './utils'
|
||||
export { CommentPopup, CommentsPresenter }
|
||||
export { createBacklinks, updateBacklinks } from './backlinks'
|
||||
export { updateBacklinks } from './backlinks'
|
||||
|
||||
async function MarkUnread (object: Message): Promise<void> {
|
||||
const client = NotificationClientImpl.getClient()
|
||||
@ -281,9 +284,12 @@ export default async (): Promise<Resources> => ({
|
||||
ChannelPresenter,
|
||||
DirectMessagePresenter,
|
||||
MessagePresenter,
|
||||
MessagePreview,
|
||||
ChannelPreview,
|
||||
ChunterBrowser,
|
||||
DmHeader,
|
||||
DmPresenter,
|
||||
DirectMessageInput,
|
||||
EditChannel,
|
||||
Threads,
|
||||
ThreadView,
|
||||
|
@ -28,7 +28,10 @@ export default mergeIds(chunterId, chunter, {
|
||||
ChannelViewPanel: '' as AnyComponent,
|
||||
ThreadViewPanel: '' as AnyComponent,
|
||||
ThreadParentPresenter: '' as AnyComponent,
|
||||
EditChannel: '' as AnyComponent
|
||||
EditChannel: '' as AnyComponent,
|
||||
ChannelPreview: '' as AnyComponent,
|
||||
MessagePreview: '' as AnyComponent,
|
||||
DirectMessageInput: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
GetDmName: '' as Resource<(client: Client, space: Space) => Promise<string>>
|
||||
@ -84,6 +87,8 @@ export default mergeIds(chunterId, chunter, {
|
||||
ChunterBrowser: '' as IntlString,
|
||||
Messages: '' as IntlString,
|
||||
NoResults: '' as IntlString,
|
||||
CopyLink: '' as IntlString
|
||||
CopyLink: '' as IntlString,
|
||||
You: '' as IntlString,
|
||||
YouHaveStartedAConversation: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -261,3 +261,22 @@ async function generateLocation (loc: Location, shortLink: string): Promise<Reso
|
||||
function isShortId (shortLink: string): boolean {
|
||||
return /^\S+-\S+$/.test(shortLink)
|
||||
}
|
||||
|
||||
export function getLinks (content: string): HTMLLinkElement[] {
|
||||
const parser = new DOMParser()
|
||||
const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement
|
||||
return parseLinks(parent.childNodes)
|
||||
}
|
||||
|
||||
function parseLinks (nodes: NodeListOf<ChildNode>): HTMLLinkElement[] {
|
||||
const res: HTMLLinkElement[] = []
|
||||
nodes.forEach((p) => {
|
||||
if (p.nodeType !== Node.TEXT_NODE) {
|
||||
if (p.nodeName === 'A') {
|
||||
res.push(p as HTMLLinkElement)
|
||||
}
|
||||
res.push(...parseLinks(p.childNodes))
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-equals": "^2.0.3",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.10",
|
||||
"@hcengineering/notification": "^0.6.14",
|
||||
|
@ -14,7 +14,17 @@
|
||||
//
|
||||
|
||||
import type { Person } from '@hcengineering/contact'
|
||||
import type { Account, AttachedDoc, Class, Doc, Ref, RelatedDocument, Space, Timestamp } from '@hcengineering/core'
|
||||
import type {
|
||||
Account,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Doc,
|
||||
Mixin,
|
||||
Ref,
|
||||
RelatedDocument,
|
||||
Space,
|
||||
Timestamp
|
||||
} from '@hcengineering/core'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { IntlString, plugin } from '@hcengineering/platform'
|
||||
@ -113,11 +123,20 @@ export interface SavedMessages extends Preference {
|
||||
attachedTo: Ref<ChunterMessage>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface DirectMessageInput extends Class<Doc> {
|
||||
component: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const chunterId = 'chunter' as Plugin
|
||||
|
||||
export * from './utils'
|
||||
|
||||
export default plugin(chunterId, {
|
||||
icon: {
|
||||
Chunter: '' as Asset,
|
||||
@ -131,6 +150,7 @@ export default plugin(chunterId, {
|
||||
CommentInput: '' as AnyComponent,
|
||||
DmHeader: '' as AnyComponent,
|
||||
ChannelView: '' as AnyComponent,
|
||||
ThreadView: '' as AnyComponent,
|
||||
CommentsPresenter: '' as AnyComponent
|
||||
},
|
||||
class: {
|
||||
@ -145,6 +165,9 @@ export default plugin(chunterId, {
|
||||
DirectMessage: '' as Ref<Class<DirectMessage>>,
|
||||
Reaction: '' as Ref<Class<Reaction>>
|
||||
},
|
||||
mixin: {
|
||||
DirectMessageInput: '' as Ref<Mixin<DirectMessageInput>>
|
||||
},
|
||||
space: {
|
||||
Backlinks: '' as Ref<Space>
|
||||
},
|
||||
@ -167,7 +190,8 @@ export default plugin(chunterId, {
|
||||
DMNotification: '' as Ref<NotificationType>,
|
||||
MentionNotification: '' as Ref<NotificationType>,
|
||||
ThreadNotification: '' as Ref<NotificationType>,
|
||||
ChannelNotification: '' as Ref<NotificationType>
|
||||
ChannelNotification: '' as Ref<NotificationType>,
|
||||
DMCreationNotification: '' as Ref<NotificationType>
|
||||
},
|
||||
app: {
|
||||
Chunter: '' as Ref<Doc>
|
||||
|
111
plugins/chunter/src/utils.ts
Normal file
111
plugins/chunter/src/utils.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import core, { Class, Data, Doc, Ref, TxOperations } from '@hcengineering/core'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
|
||||
import chunter, { Backlink, DirectMessage } from '.'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getDirectChannel (
|
||||
client: TxOperations,
|
||||
me: Ref<PersonAccount>,
|
||||
employeeAccount: Ref<PersonAccount>
|
||||
): Promise<Ref<DirectMessage>> {
|
||||
const accIds = [me, employeeAccount].sort()
|
||||
const existingDms = await client.findAll(chunter.class.DirectMessage, {})
|
||||
for (const dm of existingDms) {
|
||||
if (deepEqual(dm.members, accIds)) {
|
||||
return dm._id
|
||||
}
|
||||
}
|
||||
|
||||
return await client.createDoc(chunter.class.DirectMessage, core.space.Space, {
|
||||
name: '',
|
||||
description: '',
|
||||
private: true,
|
||||
archived: false,
|
||||
members: accIds
|
||||
})
|
||||
}
|
||||
|
||||
function extractBacklinks (
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
message: string,
|
||||
kids: NodeListOf<ChildNode>
|
||||
): Array<Data<Backlink>> {
|
||||
const result: Array<Data<Backlink>> = []
|
||||
|
||||
const nodes: Array<NodeListOf<ChildNode>> = [kids]
|
||||
while (true) {
|
||||
const nds = nodes.shift()
|
||||
if (nds === undefined) {
|
||||
break
|
||||
}
|
||||
nds.forEach((kid) => {
|
||||
if (
|
||||
kid.nodeType === Node.ELEMENT_NODE &&
|
||||
(kid as HTMLElement).localName === 'span' &&
|
||||
(kid as HTMLElement).getAttribute('data-type') === 'reference'
|
||||
) {
|
||||
const el = kid as HTMLElement
|
||||
const ato = el.getAttribute('data-id') as Ref<Doc>
|
||||
const atoClass = el.getAttribute('data-objectclass') as Ref<Class<Doc>>
|
||||
const e = result.find((e) => e.attachedTo === ato && e.attachedToClass === atoClass)
|
||||
if (e === undefined && ato !== attachedDocId && ato !== backlinkId) {
|
||||
result.push({
|
||||
attachedTo: ato,
|
||||
attachedToClass: atoClass,
|
||||
collection: 'backlinks',
|
||||
backlinkId,
|
||||
backlinkClass,
|
||||
message: el.parentElement?.innerHTML ?? '',
|
||||
attachedDocId
|
||||
})
|
||||
}
|
||||
}
|
||||
nodes.push(kid.childNodes)
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getBacklinks (
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
content: string
|
||||
): Array<Data<Backlink>> {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, 'text/html')
|
||||
return extractBacklinks(backlinkId, backlinkClass, attachedDocId, content, doc.childNodes as NodeListOf<HTMLElement>)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createBacklinks (
|
||||
client: TxOperations,
|
||||
backlinkId: Ref<Doc>,
|
||||
backlinkClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
const backlinks = getBacklinks(backlinkId, backlinkClass, attachedDocId, content)
|
||||
for (const backlink of backlinks) {
|
||||
const { attachedTo, attachedToClass, collection, ...adata } = backlink
|
||||
await client.addCollection(
|
||||
chunter.class.Backlink,
|
||||
chunter.space.Backlinks,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
collection,
|
||||
adata
|
||||
)
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@
|
||||
"Change": "Change",
|
||||
"AddedRemoved": "Added/removed",
|
||||
"YouAddedCollaborators": "You have been added to collaborators",
|
||||
"YouHaveStartedAConversation": "You have started a conversation",
|
||||
"ChangeCollaborators": "changed collaborators",
|
||||
"Activity": "Activity",
|
||||
"People": "People",
|
||||
|
@ -19,6 +19,7 @@
|
||||
"Change": "Изменено",
|
||||
"AddedRemoved": "Добавлено/удалено",
|
||||
"YouAddedCollaborators": "Вы были добавлены как участник",
|
||||
"YouHaveStartedAConversation": "Вы начали диалог",
|
||||
"ChangeCollaborators": "изменил(а) участники",
|
||||
"Activity": "Активность",
|
||||
"People": "Люди",
|
||||
|
@ -42,6 +42,7 @@
|
||||
"@hcengineering/contact": "^0.6.19",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.8",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.10",
|
||||
"@hcengineering/core": "^0.6.27",
|
||||
"@hcengineering/view": "^0.6.8",
|
||||
|
@ -13,18 +13,19 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import activity, { TxViewlet } from '@hcengineering/activity'
|
||||
import { activityKey, ActivityKey } from '@hcengineering/activity-resources'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { Employee, getName, PersonAccount } from '@hcengineering/contact'
|
||||
import { Avatar, employeeByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||
import core, { Account, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import core, { Account, Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import notification, { DocUpdates } from '@hcengineering/notification'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ActionIcon, Button, IconBack, Loading, Scroller } from '@hcengineering/ui'
|
||||
import { ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { AnySvelteComponent, Button, Loading, Scroller } from '@hcengineering/ui'
|
||||
|
||||
import NotificationView from './NotificationView.svelte'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
export let accountId: Ref<Account>
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -33,6 +34,7 @@
|
||||
|
||||
let _id: Ref<Doc> | undefined
|
||||
let docs: DocUpdates[] = []
|
||||
let filteredDocs: DocUpdates[] = []
|
||||
let loading = true
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
@ -43,30 +45,8 @@
|
||||
hidden: false
|
||||
},
|
||||
(res) => {
|
||||
docs = []
|
||||
for (const doc of res) {
|
||||
if (doc.txes.length === 0) continue
|
||||
const txes = doc.txes.filter((p) => p.modifiedBy === accountId)
|
||||
if (txes.length > 0) {
|
||||
docs.push({
|
||||
...doc,
|
||||
txes
|
||||
})
|
||||
}
|
||||
}
|
||||
docs = docs
|
||||
listProvider.update(docs)
|
||||
if (loading || _id === undefined) {
|
||||
changeSelected(selected)
|
||||
} else {
|
||||
const index = docs.findIndex((p) => p.attachedTo === _id)
|
||||
if (index === -1) {
|
||||
changeSelected(selected)
|
||||
} else {
|
||||
selected = index
|
||||
markAsRead(selected)
|
||||
}
|
||||
}
|
||||
docs = res
|
||||
updateDocs(accountId, true)
|
||||
loading = false
|
||||
},
|
||||
{
|
||||
@ -76,22 +56,43 @@
|
||||
}
|
||||
)
|
||||
|
||||
$: updateDocs(accountId)
|
||||
function updateDocs (accountId: Ref<Account>, forceUpdate = false) {
|
||||
if (loading && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
const filtered: DocUpdates[] = []
|
||||
|
||||
for (const doc of docs) {
|
||||
if (doc.txes.length === 0) continue
|
||||
const txes = doc.txes.filter((p) => p.modifiedBy === accountId)
|
||||
if (txes.length > 0) {
|
||||
filtered.push({
|
||||
...doc,
|
||||
txes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
filteredDocs = filtered
|
||||
}
|
||||
|
||||
function markAsRead (index: number) {
|
||||
if (docs[index] !== undefined) {
|
||||
docs[index].txes.forEach((p) => (p.isNew = false))
|
||||
docs[index].txes = docs[index].txes
|
||||
docs = docs
|
||||
if (filteredDocs[index] !== undefined) {
|
||||
filteredDocs[index].txes.forEach((p) => (p.isNew = false))
|
||||
filteredDocs[index].txes = filteredDocs[index].txes
|
||||
filteredDocs = filteredDocs
|
||||
}
|
||||
}
|
||||
|
||||
function changeSelected (index: number) {
|
||||
if (docs[index] !== undefined) {
|
||||
listProvider.updateFocus(docs[index])
|
||||
_id = docs[index]?.attachedTo
|
||||
dispatch('change', docs[index])
|
||||
if (filteredDocs[index] !== undefined) {
|
||||
_id = filteredDocs[index]?.attachedTo
|
||||
dispatch('change', filteredDocs[index])
|
||||
markAsRead(index)
|
||||
} else if (docs.length) {
|
||||
if (index < docs.length - 1) {
|
||||
} else if (filteredDocs.length) {
|
||||
if (index < filteredDocs.length - 1) {
|
||||
selected++
|
||||
} else {
|
||||
selected--
|
||||
@ -106,17 +107,6 @@
|
||||
|
||||
let viewlets: Map<ActivityKey, TxViewlet>
|
||||
|
||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||
if (dir === 'vertical') {
|
||||
let value = offset + docs.findIndex((p) => p._id === of?._id)
|
||||
if (value < 0) value = 0
|
||||
if (docs[value] !== undefined) {
|
||||
selected = value
|
||||
changeSelected(selected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const descriptors = createQuery()
|
||||
descriptors.query(activity.class.TxViewlet, {}, (result) => {
|
||||
viewlets = new Map(result.map((r) => [activityKey(r.objectClass, r.txClass), r]))
|
||||
@ -125,11 +115,12 @@
|
||||
let selected = 0
|
||||
|
||||
let employee: Employee | undefined = undefined
|
||||
$: newTxes = docs.reduce((acc, cur) => acc + cur.txes.filter((p) => p.isNew).length, 0) // items.length
|
||||
$: newTxes = filteredDocs.reduce((acc, cur) => acc + cur.txes.filter((p) => p.isNew).length, 0) // items.length
|
||||
$: account = $personAccountByIdStore.get(accountId as Ref<PersonAccount>)
|
||||
$: employee = account ? $employeeByIdStore.get(account.person as Ref<Employee>) : undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
async function openDM () {
|
||||
const res = await client.findAll(chunter.class.DirectMessage, { members: accountId })
|
||||
@ -167,6 +158,15 @@
|
||||
changeSelected(selected)
|
||||
}
|
||||
}
|
||||
|
||||
let dmInput: AnySvelteComponent | undefined = undefined
|
||||
$: dmInputRes = hierarchy.classHierarchyMixin(
|
||||
chunter.class.DirectMessage as Ref<Class<Doc>>,
|
||||
chunter.mixin.DirectMessageInput
|
||||
)?.component
|
||||
$: if (dmInputRes) {
|
||||
getResource(dmInputRes).then((res) => (dmInput = res))
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
@ -176,15 +176,6 @@
|
||||
/>
|
||||
<div class="flex-between header bottom-divider">
|
||||
<div class="flex-row-center">
|
||||
<div class="clear-mins flex-no-shrink mr-4">
|
||||
<ActionIcon
|
||||
icon={IconBack}
|
||||
size="medium"
|
||||
action={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if employee}
|
||||
<Avatar size="smaller" avatar={employee.avatar} />
|
||||
<span class="font-medium mx-2">{getName(client.getHierarchy(), employee)}</span>
|
||||
@ -204,21 +195,27 @@
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
{#each docs as item, i (item._id)}
|
||||
<NotificationView
|
||||
value={item}
|
||||
selected={selected === i}
|
||||
{viewlets}
|
||||
on:keydown={onKeydown}
|
||||
on:click={() => {
|
||||
selected = i
|
||||
changeSelected(selected)
|
||||
}}
|
||||
/>
|
||||
{#each filteredDocs as item, i (item._id)}
|
||||
<div class="with-hover">
|
||||
<NotificationView
|
||||
value={item}
|
||||
selected={false}
|
||||
{viewlets}
|
||||
on:keydown={onKeydown}
|
||||
on:click={() => {
|
||||
selected = i
|
||||
changeSelected(selected)
|
||||
}}
|
||||
preview
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</Scroller>
|
||||
</div>
|
||||
{#if dmInput && account}
|
||||
<svelte:component this={dmInput} {account} bind:loading />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
@ -239,4 +236,10 @@
|
||||
background-color: var(--theme-inbox-people-counter-bgcolor);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.with-hover {
|
||||
&:hover {
|
||||
background-color: var(--theme-inbox-activitymsg-bgcolor);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -13,13 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import chunter, { getDirectChannel } from '@hcengineering/chunter'
|
||||
import { Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { DocUpdates } from '@hcengineering/notification'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { AnyComponent, Component, Tabs } from '@hcengineering/ui'
|
||||
import { AnyComponent, Button, Component, IconAdd, Tabs, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { UsersPopup } from '@hcengineering/contact-resources'
|
||||
|
||||
import notification from '../plugin'
|
||||
import Activity from './Activity.svelte'
|
||||
import EmployeeInbox from './EmployeeInbox.svelte'
|
||||
@ -84,41 +87,68 @@
|
||||
}
|
||||
|
||||
let selectedTab = 0
|
||||
|
||||
const me = getCurrentAccount() as PersonAccount
|
||||
|
||||
function openUsersPopup (ev: MouseEvent) {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{ _class: contact.mixin.Employee, docQuery: { _id: { $ne: me.person } } },
|
||||
eventToHTMLElement(ev),
|
||||
async (employee: Employee) => {
|
||||
if (employee != null) {
|
||||
const personAccount = await client.findOne(contact.class.PersonAccount, { person: employee._id })
|
||||
if (personAccount !== undefined) {
|
||||
const channel = await getDirectChannel(client, me._id as Ref<PersonAccount>, personAccount._id)
|
||||
openDM(channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-top h-full">
|
||||
{#if visibileNav}
|
||||
<div class="antiPanel-component header border-right aside min-w-100 flex-no-shrink">
|
||||
{#if selectedEmployee === undefined}
|
||||
<Tabs
|
||||
bind:selected={selectedTab}
|
||||
model={tabs}
|
||||
on:change={(e) => select(e.detail)}
|
||||
on:open={(e) => {
|
||||
selectedEmployee = e.detail
|
||||
}}
|
||||
padding={'0 1.75rem'}
|
||||
size="small"
|
||||
>
|
||||
<svelte:fragment slot="rightButtons">
|
||||
<Tabs
|
||||
bind:selected={selectedTab}
|
||||
model={tabs}
|
||||
on:change={(e) => select(e.detail)}
|
||||
on:open={(e) => {
|
||||
selectedEmployee = e.detail
|
||||
select(undefined)
|
||||
}}
|
||||
padding={'0 1.75rem'}
|
||||
size="small"
|
||||
>
|
||||
<svelte:fragment slot="rightButtons">
|
||||
<div class="flex flex-gap-2">
|
||||
{#if selectedTab > 0}
|
||||
<Button label={chunter.string.Message} icon={IconAdd} kind="accented" on:click={openUsersPopup} />
|
||||
{/if}
|
||||
<Filter bind:filter />
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{:else}
|
||||
<EmployeeInbox
|
||||
accountId={selectedEmployee}
|
||||
on:change={(e) => select(e.detail)}
|
||||
on:dm={(e) => openDM(e.detail)}
|
||||
on:close={(e) => {
|
||||
selectedEmployee = undefined
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="antiPanel-component filled w-full">
|
||||
{#if component && _id && _class}
|
||||
<Component is={component} props={{ embedded: true, _id, _class }} />
|
||||
{#if selectedEmployee !== undefined && component === undefined}
|
||||
<EmployeeInbox
|
||||
accountId={selectedEmployee}
|
||||
on:change={(e) => select(e.detail)}
|
||||
on:dm={(e) => openDM(e.detail)}
|
||||
on:close={() => {
|
||||
selectedEmployee = undefined
|
||||
}}
|
||||
/>
|
||||
{:else if component && _id && _class}
|
||||
<Component
|
||||
is={component}
|
||||
props={{ _id, _class, embedded: selectedTab === 0 }}
|
||||
on:close={() => select(undefined)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -22,11 +22,13 @@
|
||||
import { AnySvelteComponent, TimeSince, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { Menu } from '@hcengineering/view-resources'
|
||||
|
||||
import TxView from './TxView.svelte'
|
||||
|
||||
export let value: DocUpdates
|
||||
export let viewlets: Map<ActivityKey, TxViewlet>
|
||||
export let selected: boolean
|
||||
export let preview: boolean = false
|
||||
|
||||
let doc: Doc | undefined = undefined
|
||||
let tx: TxCUD<Doc> | undefined = undefined
|
||||
@ -66,6 +68,21 @@
|
||||
|
||||
let div: HTMLDivElement
|
||||
$: if (selected && div !== undefined) div.focus()
|
||||
|
||||
let notificationPreviewPresenter: AnySvelteComponent | undefined = undefined
|
||||
$: notificationPreviewPresenterRes = hierarchy.classHierarchyMixin(
|
||||
value.attachedToClass,
|
||||
notification.mixin.NotificationPreview
|
||||
)?.presenter
|
||||
$: if (notificationPreviewPresenterRes) {
|
||||
getResource(notificationPreviewPresenterRes).then((res) => (notificationPreviewPresenter = res))
|
||||
}
|
||||
|
||||
let object: Doc | undefined
|
||||
const objQuery = createQuery()
|
||||
$: objQuery.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
|
||||
;[object] = res
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
@ -85,14 +102,21 @@
|
||||
<div class="counter float">{newTxes}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-between flex-baseline mt-3">
|
||||
{#if tx}
|
||||
<TxView {tx} {viewlets} objectId={value.attachedTo} />
|
||||
{/if}
|
||||
<div class="time">
|
||||
<TimeSince value={tx?.modifiedOn} />
|
||||
{#if preview && object && notificationPreviewPresenter !== undefined}
|
||||
<div class="mt-2">
|
||||
<svelte:component this={notificationPreviewPresenter} {object} {newTxes} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !preview || notificationPreviewPresenter === undefined}
|
||||
<div class="flex-between flex-baseline mt-3">
|
||||
{#if tx}
|
||||
<TxView {tx} {viewlets} objectId={value.attachedTo} />
|
||||
{/if}
|
||||
<div class="time">
|
||||
<TimeSince value={tx?.modifiedOn} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import activity, { TxViewlet } from '@hcengineering/activity'
|
||||
import { activityKey, ActivityKey } from '@hcengineering/activity-resources'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
@ -21,7 +22,7 @@
|
||||
import notification, { DocUpdates } from '@hcengineering/notification'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Loading, Scroller } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import PeopleNotificationView from './PeopleNotificationsView.svelte'
|
||||
|
||||
export let filter: 'all' | 'read' | 'unread' = 'all'
|
||||
@ -40,9 +41,9 @@
|
||||
user: getCurrentAccount()._id,
|
||||
hidden: false
|
||||
},
|
||||
(res) => {
|
||||
async (res) => {
|
||||
docs = res
|
||||
getFiltered(docs, filter)
|
||||
await getFiltered(docs, filter)
|
||||
loading = false
|
||||
},
|
||||
{
|
||||
@ -52,7 +53,7 @@
|
||||
}
|
||||
)
|
||||
|
||||
function getFiltered (docs: DocUpdates[], filter: 'all' | 'read' | 'unread'): void {
|
||||
async function getFiltered (docs: DocUpdates[], filter: 'all' | 'read' | 'unread'): Promise<void> {
|
||||
const filtered: DocUpdates[] = []
|
||||
for (const doc of docs) {
|
||||
if (doc.txes.length === 0) continue
|
||||
@ -169,7 +170,7 @@
|
||||
{viewlets}
|
||||
on:keydown={onKeydown}
|
||||
on:open
|
||||
on:click={() => {
|
||||
on:open={() => {
|
||||
selected = i
|
||||
}}
|
||||
/>
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { TxViewlet } from '@hcengineering/activity'
|
||||
import { ActivityKey } from '@hcengineering/activity-resources'
|
||||
import { PersonAccount, getName } from '@hcengineering/contact'
|
||||
@ -23,7 +24,7 @@
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ActionIcon, AnySvelteComponent, Label, TimeSince } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import TxView from './TxView.svelte'
|
||||
import ArrowRight from './icons/ArrowRight.svelte'
|
||||
|
||||
@ -71,7 +72,7 @@
|
||||
|
||||
let div: HTMLDivElement
|
||||
|
||||
$: if (selected && div !== undefined) div.focus()
|
||||
$: if (selected && div != null) div.focus()
|
||||
</script>
|
||||
|
||||
{#if doc}
|
||||
|
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
// 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 { TxCreateDoc } from '@hcengineering/core'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { DirectMessage } from '@hcengineering/chunter'
|
||||
|
||||
import notification from '../../plugin'
|
||||
|
||||
export let tx: TxCreateDoc<DirectMessage>
|
||||
export let value: DirectMessage
|
||||
</script>
|
||||
|
||||
<Label label={notification.string.YouHaveStartedAConversation} />
|
@ -19,6 +19,7 @@ import Inbox from './components/Inbox.svelte'
|
||||
import NotificationSettings from './components/NotificationSettings.svelte'
|
||||
import NotificationPresenter from './components/NotificationPresenter.svelte'
|
||||
import TxCollaboratorsChange from './components/activity/TxCollaboratorsChange.svelte'
|
||||
import TxDmCreation from './components/activity/TxDmCreation.svelte'
|
||||
import { NotificationClientImpl, hasntNotifications, hide, markAsUnread, unsubscribe } from './utils'
|
||||
|
||||
export * from './utils'
|
||||
@ -32,7 +33,8 @@ export default async (): Promise<Resources> => ({
|
||||
NotificationSettings
|
||||
},
|
||||
activity: {
|
||||
TxCollaboratorsChange
|
||||
TxCollaboratorsChange,
|
||||
TxDmCreation
|
||||
},
|
||||
function: {
|
||||
GetNotificationClient: NotificationClientImpl.getClient,
|
||||
|
@ -30,6 +30,7 @@ export default mergeIds(notificationId, notification, {
|
||||
Change: '' as IntlString,
|
||||
AddedRemoved: '' as IntlString,
|
||||
YouAddedCollaborators: '' as IntlString,
|
||||
YouHaveStartedAConversation: '' as IntlString,
|
||||
ChangeCollaborators: '' as IntlString,
|
||||
Activity: '' as IntlString,
|
||||
People: '' as IntlString,
|
||||
|
@ -189,6 +189,13 @@ export interface NotificationClient {
|
||||
forceRead: (_id: Ref<Doc>, _class: Ref<Class<Doc>>, space: Ref<Space>) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NotificationPreview extends Class<Doc> {
|
||||
presenter: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -201,7 +208,8 @@ const notification = plugin(notificationId, {
|
||||
mixin: {
|
||||
ClassCollaborators: '' as Ref<Mixin<ClassCollaborators>>,
|
||||
Collaborators: '' as Ref<Mixin<Collaborators>>,
|
||||
NotificationObjectPresenter: '' as Ref<Mixin<NotificationObjectPresenter>>
|
||||
NotificationObjectPresenter: '' as Ref<Mixin<NotificationObjectPresenter>>,
|
||||
NotificationPreview: '' as Ref<Mixin<NotificationPreview>>
|
||||
},
|
||||
class: {
|
||||
Notification: '' as Ref<Class<Notification>>,
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import chunter, { createBacklinks } from '@hcengineering/chunter'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import core, { Account, AttachedData, Doc, fillDefaults, generateId, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
@ -70,7 +70,6 @@
|
||||
import MilestoneSelector from './milestones/MilestoneSelector.svelte'
|
||||
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
|
||||
import SubIssues from './SubIssues.svelte'
|
||||
import { createBacklinks } from '@hcengineering/chunter-resources'
|
||||
import ProjectPresenter from './projects/ProjectPresenter.svelte'
|
||||
|
||||
export let space: Ref<Project>
|
||||
|
@ -15,6 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Data, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createBacklinks } from '@hcengineering/chunter'
|
||||
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
|
||||
import { Milestone, MilestoneStatus, Project } from '@hcengineering/tracker'
|
||||
import ui, { DatePresenter, EditBox } from '@hcengineering/ui'
|
||||
@ -22,7 +23,6 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import MilestoneStatusEditor from './MilestoneStatusEditor.svelte'
|
||||
import { createBacklinks } from '@hcengineering/chunter-resources'
|
||||
import ProjectPresenter from '../projects/ProjectPresenter.svelte'
|
||||
|
||||
export let space: Ref<Project>
|
||||
|
@ -457,7 +457,7 @@
|
||||
specialComponent = getSpecialComponent(spaceSpecial)
|
||||
if (specialComponent !== undefined) {
|
||||
currentSpecial = spaceSpecial
|
||||
} else if (navigatorModel?.aside !== undefined) {
|
||||
} else if (navigatorModel?.aside !== undefined || currentApplication?.aside !== undefined) {
|
||||
asideId = spaceSpecial
|
||||
}
|
||||
}
|
||||
@ -760,17 +760,20 @@
|
||||
<SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if asideId && navigatorModel?.aside !== undefined}
|
||||
<div class="splitter" class:hovered={isResizing} on:mousedown={startResize} />
|
||||
<div
|
||||
class="antiPanel-component antiComponent aside"
|
||||
use:resizeObserver={(element) => {
|
||||
asideWidth = element.clientWidth
|
||||
}}
|
||||
bind:this={aside}
|
||||
>
|
||||
<Component is={navigatorModel.aside} props={{ currentSpace, _id: asideId }} on:close={closeAside} />
|
||||
</div>
|
||||
{#if asideId}
|
||||
{@const asideComponent = navigatorModel?.aside ?? currentApplication?.aside}
|
||||
{#if asideComponent !== undefined}
|
||||
<div class="splitter" class:hovered={isResizing} on:mousedown={startResize} />
|
||||
<div
|
||||
class="antiPanel-component antiComponent aside"
|
||||
use:resizeObserver={(element) => {
|
||||
asideWidth = element.clientWidth
|
||||
}}
|
||||
bind:this={aside}
|
||||
>
|
||||
<Component is={asideComponent} props={{ currentSpace, _id: asideId }} on:close={closeAside} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,6 +31,7 @@ export interface Application extends Doc {
|
||||
|
||||
// Also attached ApplicationNavModel will be joined after this one main.
|
||||
navigatorModel?: NavigatorModel
|
||||
aside?: AnyComponent
|
||||
|
||||
locationResolver?: Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
||||
|
||||
|
@ -13,7 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import chunter, { Backlink, chunterId, ChunterSpace, Comment, Message, ThreadMessage } from '@hcengineering/chunter'
|
||||
import chunter, {
|
||||
Backlink,
|
||||
chunterId,
|
||||
ChunterSpace,
|
||||
Comment,
|
||||
DirectMessage,
|
||||
Message,
|
||||
ThreadMessage
|
||||
} from '@hcengineering/chunter'
|
||||
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
@ -36,7 +44,7 @@ import core, {
|
||||
import notification, { Collaborators, NotificationType } from '@hcengineering/notification'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getDocCollaborators, getMixinTx } from '@hcengineering/server-notification-resources'
|
||||
import { pushNotification, getDocCollaborators, getMixinTx } from '@hcengineering/server-notification-resources'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
/**
|
||||
@ -206,6 +214,34 @@ export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<
|
||||
return res.flat()
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDmCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ptx = tx as TxCreateDoc<DirectMessage>
|
||||
const res: Tx[] = []
|
||||
|
||||
if (tx.createdBy == null) return []
|
||||
|
||||
const dm = TxProcessor.createDoc2Doc(ptx)
|
||||
|
||||
if (dm.members.length > 2) return []
|
||||
|
||||
let dmWithPerson: Ref<Account> | undefined
|
||||
for (const person of dm.members) {
|
||||
if (person !== tx.createdBy) {
|
||||
dmWithPerson = person
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (dmWithPerson == null) return []
|
||||
|
||||
pushNotification(control, res, tx.createdBy, dm, ptx, [], dmWithPerson)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -272,7 +308,8 @@ export async function IsChannelMessage (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
ChunterTrigger
|
||||
ChunterTrigger,
|
||||
OnDmCreate
|
||||
},
|
||||
function: {
|
||||
CommentRemove,
|
||||
|
@ -29,7 +29,8 @@ export const serverChunterId = 'server-chunter' as Plugin
|
||||
*/
|
||||
export default plugin(serverChunterId, {
|
||||
trigger: {
|
||||
ChunterTrigger: '' as Resource<TriggerFunc>
|
||||
ChunterTrigger: '' as Resource<TriggerFunc>,
|
||||
OnDmCreate: '' as Resource<TriggerFunc>
|
||||
},
|
||||
function: {
|
||||
CommentRemove: '' as Resource<
|
||||
|
@ -432,13 +432,17 @@ async function isShouldNotify (
|
||||
}
|
||||
}
|
||||
|
||||
function pushNotification (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function pushNotification (
|
||||
control: TriggerControl,
|
||||
res: Tx[],
|
||||
target: Ref<Account>,
|
||||
object: Doc,
|
||||
originTx: TxCUD<Doc>,
|
||||
docUpdates: DocUpdates[]
|
||||
docUpdates: DocUpdates[],
|
||||
modifiedBy?: Ref<Account>
|
||||
): void {
|
||||
const current = docUpdates.find((p) => p.user === target)
|
||||
if (current === undefined) {
|
||||
@ -449,14 +453,26 @@ function pushNotification (
|
||||
attachedToClass: object._class,
|
||||
hidden: false,
|
||||
lastTxTime: originTx.modifiedOn,
|
||||
txes: [{ _id: originTx._id, modifiedOn: originTx.modifiedOn, modifiedBy: originTx.modifiedBy, isNew: true }]
|
||||
txes: [
|
||||
{
|
||||
_id: originTx._id,
|
||||
modifiedOn: originTx.modifiedOn,
|
||||
modifiedBy: modifiedBy ?? originTx.modifiedBy,
|
||||
isNew: true
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
} else {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
$push: {
|
||||
txes: { _id: originTx._id, modifiedOn: originTx.modifiedOn, modifiedBy: originTx.modifiedBy, isNew: true }
|
||||
txes: {
|
||||
_id: originTx._id,
|
||||
modifiedOn: originTx.modifiedOn,
|
||||
modifiedBy: modifiedBy ?? originTx.modifiedBy,
|
||||
isNew: true
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user