[Part 2]: Provide more spaces to queries/finds (#6230)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-08-02 13:24:18 +04:00 committed by GitHub
parent b5ee82cf1a
commit 7860f06aae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 401 additions and 265 deletions

View File

@ -687,7 +687,11 @@ export function isAdminUser (): boolean {
}
export function isSpace (space: Doc): space is Space {
return getClient().getHierarchy().isDerived(space._class, core.class.Space)
return isSpaceClass(space._class)
}
export function isSpaceClass (_class: Ref<Class<Doc>>): boolean {
return getClient().getHierarchy().isDerived(_class, core.class.Space)
}
export function setPresentationCookie (token: string, workspaceId: string): void {

View File

@ -1,5 +1,5 @@
import activity, { type ActivityMessage, type SavedMessage } from '@hcengineering/activity'
import { type Ref, SortingOrder, type WithLookup } from '@hcengineering/core'
import core, { type Ref, SortingOrder, type WithLookup } from '@hcengineering/core'
import { writable } from 'svelte/store'
import { createQuery, getClient } from '@hcengineering/presentation'
@ -14,7 +14,7 @@ export function loadSavedMessages (): void {
if (client !== undefined) {
savedMessagesQuery.query(
activity.class.SavedMessage,
{},
{ space: core.space.Workspace },
(res) => {
savedMessagesStore.set(res.filter(({ $lookup }) => $lookup?.attachedTo !== undefined))
},

View File

@ -13,20 +13,26 @@
// limitations under the License.
-->
<script lang="ts">
import activity, { ActivityExtension, ActivityMessage, DisplayActivityMessage } from '@hcengineering/activity'
import activity, {
ActivityExtension,
ActivityMessage,
ActivityReference,
DisplayActivityMessage,
WithReferences
} from '@hcengineering/activity'
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery, getClient, isSpace } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Grid, Label, Spinner, location, Lazy } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte'
import ActivityExtensionComponent from './ActivityExtension.svelte'
import ActivityFilter from './ActivityFilter.svelte'
import { combineActivityMessages } from '../activityMessagesUtils'
import { canGroupMessages, getMessageFromLoc } from '../utils'
import { combineActivityMessages, sortActivityMessages } from '../activityMessagesUtils'
import { canGroupMessages, getMessageFromLoc, getSpace } from '../utils'
import ActivityMessagePresenter from './activity-message/ActivityMessagePresenter.svelte'
import { messageInFocus } from '../activity'
export let object: Doc
export let object: WithReferences<Doc>
export let showCommenInput: boolean = true
export let transparent: boolean = false
export let focusIndex: number = -1
@ -34,12 +40,17 @@
const client = getClient()
const activityMessagesQuery = createQuery()
const refsQuery = createQuery()
let extensions: ActivityExtension[] = []
let filteredMessages: DisplayActivityMessage[] = []
let activityMessages: ActivityMessage[] = []
let isLoading = false
let allMessages: ActivityMessage[] = []
let messages: ActivityMessage[] = []
let refs: ActivityReference[] = []
let isMessagesLoading = false
let isRefsLoading = true
let activityBox: HTMLElement | undefined
let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
@ -163,16 +174,38 @@
extensions = res
})
// Load references from other spaces separately because they can have any different spaces
$: if ((object.references ?? 0) > 0) {
refsQuery.query(
activity.class.ActivityReference,
{ attachedTo: object._id, space: { $ne: getSpace(object) } },
(res) => {
refs = res
isRefsLoading = false
},
{
sort: {
createdOn: SortingOrder.Ascending
}
}
)
} else {
isRefsLoading = false
refsQuery.unsubscribe()
}
$: allMessages = sortActivityMessages(messages.concat(refs))
async function updateActivityMessages (objectId: Ref<Doc>, order: SortingOrder): Promise<void> {
isLoading = true
isMessagesLoading = true
const res = activityMessagesQuery.query(
activity.class.ActivityMessage,
{ attachedTo: objectId, space: isSpace(object) ? object._id : object.space },
{ attachedTo: objectId, space: getSpace(object) },
(result: ActivityMessage[]) => {
void combineActivityMessages(result, order).then((messages) => {
activityMessages = messages
isLoading = false
void combineActivityMessages(result, order).then((res) => {
messages = res
isMessagesLoading = false
})
},
{
@ -182,10 +215,11 @@
}
)
if (!res) {
isLoading = false
isMessagesLoading = false
}
}
$: isLoading = isMessagesLoading || isRefsLoading
$: areMessagesLoaded = !isLoading && filteredMessages.length > 0
$: if (activityBox && areMessagesLoaded) {
@ -206,7 +240,7 @@
{/if}
</span>
<ActivityFilter
messages={activityMessages}
messages={allMessages}
{object}
on:update={(e) => {
filteredMessages = e.detail

View File

@ -16,8 +16,8 @@
import { Label, tooltip } from '@hcengineering/ui'
import { DocNotifyContext } from '@hcengineering/notification'
import activity, { ActivityMessage } from '@hcengineering/activity'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Doc, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Doc } from '@hcengineering/core'
import { getDocLinkTitle, getDocTitle, ObjectIcon } from '@hcengineering/view-resources'
import { getEmbeddedLabel } from '@hcengineering/platform'
import contact from '@hcengineering/contact'
@ -25,54 +25,44 @@
import ActivityMessagePreview from './ActivityMessagePreview.svelte'
export let context: DocNotifyContext
export let object: ActivityMessage
const client = getClient()
const hierarchy = client.getHierarchy()
const parentQuery = createQuery()
let parentMessage: ActivityMessage | undefined = undefined
let title: string | undefined = undefined
let object: Doc | undefined = undefined
$: parentQuery.query(activity.class.ActivityMessage, { _id: context.attachedTo as Ref<ActivityMessage> }, (res) => {
parentMessage = res[0]
})
$: parentMessage &&
client.findOne(parentMessage.attachedToClass, { _id: parentMessage.attachedTo }).then((res) => {
object = res
})
let doc: Doc | undefined = undefined
$: object &&
getDocLinkTitle(client, object._id, object._class, object).then((res) => {
client.findOne(object.attachedToClass, { _id: object.attachedTo, space: object.space }).then((res) => {
doc = res
})
$: doc &&
getDocLinkTitle(client, doc._id, doc._class, doc).then((res) => {
title = res
})
</script>
{#if parentMessage}
<span class="flex-presenter flex-gap-1 font-semi-bold">
<Label label={(parentMessage?.replies ?? 0) > 0 ? activity.string.Thread : activity.string.Message} />
{#if title}
<span class="lower">
<Label label={activity.string.In} />
</span>
{#if object}
{#await getDocTitle(client, object._id, object._class, object) then tooltipLabel}
<span
class="flex-presenter flex-gap-0-5"
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
>
<ObjectIcon
value={object}
size={hierarchy.isDerived(object._class, contact.class.Person) ? 'tiny' : 'small'}
/>
{title}
</span>
{/await}
{/if}
<span class="flex-presenter flex-gap-1 font-semi-bold">
<Label label={(object?.replies ?? 0) > 0 ? activity.string.Thread : activity.string.Message} />
{#if title}
<span class="lower">
<Label label={activity.string.In} />
</span>
{#if doc}
{#await getDocTitle(client, doc._id, doc._class, doc) then tooltipLabel}
<span
class="flex-presenter flex-gap-0-5"
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
>
<ObjectIcon value={doc} size={hierarchy.isDerived(doc._class, contact.class.Person) ? 'tiny' : 'small'} />
{title}
</span>
{/await}
{/if}
</span>
<span class="font-normal">
<ActivityMessagePreview value={parentMessage} readonly type="content-only" />
</span>
{/if}
{/if}
</span>
<span class="font-normal">
<ActivityMessagePreview value={object} {doc} readonly type="content-only" />
</span>

View File

@ -21,6 +21,7 @@
import activity from '../../plugin'
export let doc: Doc | undefined
export let value: DisplayActivityMessage
export let readonly = false
export let type: ActivityMessagePreviewType = 'full'
@ -44,7 +45,8 @@
type,
readonly,
actions,
space
space,
doc
}}
on:click
/>

View File

@ -13,22 +13,10 @@
// limitations under the License.
-->
<script lang="ts">
import { createQuery, MessageViewer } from '@hcengineering/presentation'
import { Ref } from '@hcengineering/core'
import activity, { ActivityReference } from '@hcengineering/activity'
import { MessageViewer } from '@hcengineering/presentation'
import { ActivityReference } from '@hcengineering/activity'
export let _id: Ref<ActivityReference> | undefined = undefined
export let value: ActivityReference | undefined = undefined
const query = createQuery()
$: if (value === undefined && _id !== undefined) {
query.query(activity.class.ActivityReference, { _id }, (res) => {
value = res.shift()
})
} else {
query.unsubscribe()
}
</script>
{#if value}

View File

@ -16,12 +16,12 @@
import type { Doc } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { DocReferencePresenter } from '@hcengineering/view-resources'
import activity from '../../plugin'
import { isActivityMessage } from '../../activityMessagesUtils'
import view from '@hcengineering/view'
import { Icon, Label } from '@hcengineering/ui'
import activity from '../../plugin'
import { isActivityMessage } from '../../activityMessagesUtils'
export let value: Doc | undefined
const client = getClient()
@ -31,7 +31,7 @@
$: showParent = isActivityMessage(value)
$: isActivityMessage(value) &&
client.findOne(value.attachedToClass, { _id: value.attachedTo }).then((res) => {
client.findOne(value.attachedToClass, { _id: value.attachedTo, space: value.space }).then((res) => {
parentObject = res
})
</script>

View File

@ -88,13 +88,17 @@
attributeModel = model
})
async function getParentMessage (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<ActivityMessage | undefined> {
async function getParentMessage (
_class: Ref<Class<Doc>>,
_id: Ref<Doc>,
space: Ref<Space>
): Promise<ActivityMessage | undefined> {
if (hierarchy.isDerived(_class, activity.class.ActivityMessage)) {
return await client.findOne(activity.class.ActivityMessage, { _id: _id as Ref<ActivityMessage> })
return await client.findOne(activity.class.ActivityMessage, { _id: _id as Ref<ActivityMessage>, space })
}
}
$: void getParentMessage(value.attachedToClass, value.attachedTo).then((res) => {
$: void getParentMessage(value.attachedToClass, value.attachedTo, value.space).then((res) => {
parentMessage = res as DisplayActivityMessage
})
@ -150,6 +154,7 @@
const _id = parentMessage ? parentMessage.attachedTo : message.attachedTo
const _class = parentMessage ? parentMessage.attachedToClass : message.attachedToClass
const space = parentMessage ? parentMessage.space : message.space
if (doc !== undefined && doc._id === _id) {
parentObject = doc
@ -163,7 +168,7 @@
return
}
parentObjectQuery.query(_class, { _id }, (res) => {
parentObjectQuery.query(_class, { _id, space }, (res) => {
parentObject = res[0]
})
}

View File

@ -32,6 +32,7 @@
import DocUpdateMessageContent from './DocUpdateMessageContent.svelte'
import DocUpdateMessageAttributes from './DocUpdateMessageAttributes.svelte'
export let doc: Doc | undefined
export let value: DisplayDocUpdateMessage
export let readonly = false
export let type: ActivityMessagePreviewType = 'full'
@ -65,15 +66,20 @@
attributeModel = model
})
$: viewlet?.component && loadObject(value.objectId, value.objectClass)
$: viewlet?.component && loadObject(value.objectId, value.objectClass, value.space, doc)
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>, space: Ref<Space>, doc?: Doc): Promise<void> {
if (doc?._id === _id) {
object = doc
return
}
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>): Promise<void> {
const isObjectRemoved = await checkIsObjectRemoved(client, _id, _class)
if (isObjectRemoved) {
object = await buildRemovedDoc(client, _id, _class)
} else {
objectQuery.query(_class, { _id }, (res) => {
objectQuery.query(_class, { _id, space }, (res) => {
object = res[0]
})
}

View File

@ -14,18 +14,19 @@
-->
<script lang="ts">
import { Reaction } from '@hcengineering/activity'
import { Ref } from '@hcengineering/core'
import { Ref, Space } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import activity from '../../plugin'
export let _id: Ref<Reaction>
export let space: Ref<Space>
export let value: Reaction | undefined = undefined
const query = createQuery()
$: value === undefined &&
query.query(activity.class.Reaction, { _id }, (res) => {
query.query(activity.class.Reaction, { _id, space }, (res) => {
value = res[0]
})
</script>

View File

@ -16,7 +16,7 @@
import activity, { ActivityMessage, Reaction } from '@hcengineering/activity'
import { createQuery, getClient } from '@hcengineering/presentation'
import { updateDocReactions } from '../../utils'
import { getSpace, updateDocReactions } from '../../utils'
import Reactions from './Reactions.svelte'
export let object: ActivityMessage | undefined
@ -30,9 +30,13 @@
$: hasReactions = object?.reactions && object.reactions > 0
$: if (object && hasReactions) {
reactionsQuery.query(activity.class.Reaction, { attachedTo: object._id }, (res: Reaction[]) => {
reactions = res
})
reactionsQuery.query(
activity.class.Reaction,
{ attachedTo: object._id, space: getSpace(object) },
(res: Reaction[]) => {
reactions = res
}
)
} else {
reactionsQuery.unsubscribe()
}

View File

@ -37,7 +37,7 @@
$: if (message && hasReactions) {
reactionsQuery.query(
activity.class.Reaction,
{ attachedTo: message._id },
{ attachedTo: message._id, space: message.space },
(res: Reaction[]) => {
reactions = res

View File

@ -1,6 +1,13 @@
import type { ActivityMessage, Reaction } from '@hcengineering/activity'
import core, { getCurrentAccount, isOtherHour, type Doc, type Ref, type TxOperations } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import core, {
getCurrentAccount,
isOtherHour,
type Doc,
type Ref,
type TxOperations,
type Space
} from '@hcengineering/core'
import { getClient, isSpace } from '@hcengineering/presentation'
import {
EmojiPopup,
closePopup,
@ -58,7 +65,7 @@ export async function addReactionAction (
const client = getClient()
const reactions: Reaction[] =
(message.reactions ?? 0) > 0
? await client.findAll<Reaction>(activity.class.Reaction, { attachedTo: message._id })
? await client.findAll<Reaction>(activity.class.Reaction, { attachedTo: message._id, space: message.space })
: []
const element = getEventPositionElement(ev)
@ -169,3 +176,7 @@ export function shouldScrollToActivity (): boolean {
const loc = getCurrentResolvedLocation()
return getMessageFromLoc(loc) !== undefined
}
export function getSpace (doc: Doc): Ref<Space> {
return isSpace(doc) ? doc._id : doc.space
}

View File

@ -235,6 +235,10 @@ export interface UserMentionInfo extends AttachedDoc {
content: string
}
export type WithReferences<T extends Doc> = T & {
references?: number
}
/**
* @public
*/

View File

@ -25,11 +25,10 @@ import {
type Space,
type Timestamp
} from '@hcengineering/core'
import { derived, get, type Readable, writable } from 'svelte/store'
import { type ActivityMessage } from '@hcengineering/activity'
import activity, { type ActivityMessage, type ActivityReference } from '@hcengineering/activity'
import attachment from '@hcengineering/attachment'
import { combineActivityMessages } from '@hcengineering/activity-resources'
import { combineActivityMessages, sortActivityMessages } from '@hcengineering/activity-resources'
import { type ChatMessage } from '@hcengineering/chunter'
import notification, { type DocNotifyContext } from '@hcengineering/notification'
@ -70,6 +69,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
private readonly metadataQuery = createQuery(true)
private readonly tailQuery = createQuery(true)
private readonly refsQuery = createQuery(true)
private chatId: Ref<Doc> | undefined = undefined
private readonly msgClass: Ref<Class<ActivityMessage>>
@ -79,12 +79,14 @@ export class ChannelDataProvider implements IChannelDataProvider {
public readonly metadataStore = writable<MessageMetadata[]>([])
private readonly tailStore = writable<ActivityMessage[]>([])
private readonly chunksStore = writable<Chunk[]>([])
public readonly refsStore = writable<ActivityReference[]>([])
private readonly isInitialLoadingStore = writable(false)
private readonly isInitialLoadedStore = writable(false)
private readonly isTailLoading = writable(false)
readonly isTailLoaded = writable(false)
readonly isRefsLoading = writable(false)
public datesStore = writable<Timestamp[]>([])
public newTimestampStore = writable<Timestamp | undefined>(undefined)
@ -92,8 +94,8 @@ export class ChannelDataProvider implements IChannelDataProvider {
public isLoadingMoreStore = writable(false)
public isLoadingStore = derived(
[this.isInitialLoadedStore, this.isTailLoading],
([initialLoaded, tailLoading]) => !initialLoaded || tailLoading
[this.isInitialLoadedStore, this.isTailLoading, this.isRefsLoading],
([initialLoaded, tailLoading, isRefsLoading]) => !initialLoaded || tailLoading || isRefsLoading
)
private readonly backwardNextStore = writable<Chunk | undefined>(undefined)
@ -107,8 +109,9 @@ export class ChannelDataProvider implements IChannelDataProvider {
private nextChunkAdding = false
public messagesStore = derived([this.chunksStore, this.tailStore], ([chunks, tail]) => {
return [...chunks.map(({ data }) => data).flat(), ...tail]
public messagesStore = derived([this.chunksStore, this.tailStore, this.refsStore], ([chunks, tail, refs]) => {
const data = chunks.map(({ data, to, from }) => mergeWithRefs(data, refs, from, to))
return [...data.flat(), ...mergeWithRefs(tail, refs, tail[0]?.createdOn)]
})
public canLoadNextForwardStore = derived([this.messagesStore, this.forwardNextStore], ([messages, forwardNext]) => {
@ -123,18 +126,20 @@ export class ChannelDataProvider implements IChannelDataProvider {
chatId: Ref<Doc>,
_class: Ref<Class<ActivityMessage>>,
selectedMsgId: Ref<ActivityMessage> | undefined,
loadAll = false
loadAll = false,
withRefs = false
) {
this.chatId = chatId
this.msgClass = _class
this.selectedMsgId = selectedMsgId
void this.loadData(loadAll)
void this.loadData(loadAll, withRefs)
}
public destroy (): void {
this.clearData()
this.metadataQuery.unsubscribe()
this.tailQuery.unsubscribe()
this.refsQuery.unsubscribe()
}
public canLoadMore (mode: LoadMode, timestamp?: Timestamp): boolean {
@ -168,11 +173,29 @@ export class ChannelDataProvider implements IChannelDataProvider {
this.clearMessages()
}
private async loadData (loadAll = false): Promise<void> {
loadRefs (): void {
// Load references from other spaces separately because they can have any different spaces
this.refsQuery.query(
activity.class.ActivityReference,
{ attachedTo: this.chatId, space: { $ne: this.space } },
(res) => {
this.refsStore.set(res)
this.isRefsLoading.set(false)
},
{ sort: { createdOn: SortingOrder.Ascending } }
)
}
private async loadData (loadAll = false, withRefs = false): Promise<void> {
if (this.chatId === undefined) {
return
}
if (withRefs && this.msgClass === activity.class.ActivityMessage) {
this.isRefsLoading.set(true)
this.loadRefs()
}
this.metadataQuery.query(
this.msgClass,
{ attachedTo: this.chatId, space: this.space },
@ -622,3 +645,21 @@ export class ChannelDataProvider implements IChannelDataProvider {
return true
}
}
function mergeWithRefs (
messages: ActivityMessage[],
refs: ActivityReference[],
from?: Timestamp,
to?: Timestamp
): ActivityMessage[] {
if (from === undefined) return messages
if (refs.length === 0) return messages
const refsFiltered = refs.filter(
({ createdOn }) => (createdOn ?? 0) >= from && (to === undefined || (createdOn ?? 0) <= to)
)
if (refsFiltered.length === 0) return messages
return sortActivityMessages(messages.concat(refsFiltered))
}

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import notification, { DocNotifyContext } from '@hcengineering/notification'
import activity, { ActivityMessage, ActivityMessagesFilter } from '@hcengineering/activity'
import activity, { ActivityMessage, ActivityMessagesFilter, WithReferences } from '@hcengineering/activity'
import { getClient, isSpace } from '@hcengineering/presentation'
import { getMessageFromLoc, messageInFocus } from '@hcengineering/activity-resources'
import { location as locationStore } from '@hcengineering/ui'
@ -57,6 +57,8 @@
dataProvider = undefined
})
let refsLoaded = false
$: isDocChannel = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
$: _class = isDocChannel ? activity.class.ActivityMessage : chunter.class.ChatMessage
$: collection = isDocChannel ? 'comments' : 'messages'
@ -78,11 +80,17 @@
attachedTo: object._id,
user: getCurrentAccount()._id
}))
const hasRefs = ((object as WithReferences<Doc>).references ?? 0) > 0
refsLoaded = hasRefs
const space = isSpace(object) ? object._id : object.space
dataProvider = new ChannelDataProvider(ctx, space, attachedTo, _class, selectedMessageId, loadAll)
dataProvider = new ChannelDataProvider(ctx, space, attachedTo, _class, selectedMessageId, loadAll, hasRefs)
}
}
$: if (dataProvider && !refsLoaded && ((object as WithReferences<Doc>).references ?? 0) > 0) {
dataProvider.loadRefs()
refsLoaded = true
}
</script>
{#if dataProvider}

View File

@ -17,7 +17,7 @@
import { getDocTitle } from '@hcengineering/view-resources'
import { getClient } from '@hcengineering/presentation'
import { Channel } from '@hcengineering/chunter'
import { ActivityMessagesFilter } from '@hcengineering/activity'
import { ActivityMessagesFilter, WithReferences } from '@hcengineering/activity'
import contact from '@hcengineering/contact'
import Header from './Header.svelte'
@ -27,7 +27,7 @@
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
export let object: Doc | undefined
export let object: WithReferences<Doc> | undefined
export let allowClose: boolean = false
export let canOpen: boolean = false
export let withAside: boolean = false
@ -78,6 +78,6 @@
on:close
>
{#if object}
<PinnedMessages {_id} {_class} space={object.space} on:select />
<PinnedMessages {_id} {_class} space={object.space} withRefs={(object.references ?? 0) > 0} on:select />
{/if}
</Header>

View File

@ -646,10 +646,7 @@
return
}
const lastMetadata = metadata[metadata.length - 1]
const lastMessage = displayMessages[displayMessages.length - 1]
if (lastMetadata._id !== lastMessage._id) {
if (!$isTailLoadedStore) {
showScrollDownButton = true
} else if (element != null) {
const { scrollHeight, scrollTop, offsetHeight } = element

View File

@ -28,13 +28,16 @@
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
export let _id: Ref<Doc>
export let withRefs = false
const dispatch = createEventDispatcher()
const pinnedQuery = createQuery()
const pinnedThreadsQuery = createQuery()
const pinnedRefsQuery = createQuery()
let pinnedMessagesCount = 0
let pinnedThreadsCount = 0
let refsCount = 0
$: channelSpace = getChannelSpace(_class, _id, space)
$: pinnedQuery.query(
@ -55,10 +58,21 @@
{ projection: { _id: 1, space: 1, objectId: 1, isPinned: 1 } }
)
$: if (withRefs) {
pinnedRefsQuery.query(
activity.class.ActivityReference,
{ attachedTo: _id, isPinned: true, space: { $ne: channelSpace } },
(res) => {
refsCount = res.total
},
{ limit: 1, total: true }
)
}
function openMessagesPopup (ev: MouseEvent): void {
showPopup(
PinnedMessagesPopup,
{ attachedTo: _id, attachedToClass: _class, space: channelSpace },
{ attachedTo: _id, attachedToClass: _class, space: channelSpace, withRefs },
eventToHTMLElement(ev),
(result) => {
if (result == null) return
@ -67,7 +81,7 @@
)
}
$: count = pinnedMessagesCount + pinnedThreadsCount
$: count = pinnedMessagesCount + pinnedThreadsCount + refsCount
</script>
{#if count > 0}

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { createQuery, getClient } from '@hcengineering/presentation'
import activity, { ActivityMessage } from '@hcengineering/activity'
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity'
import { ActivityMessagePresenter, sortActivityMessages } from '@hcengineering/activity-resources'
import { ActionIcon, IconClose } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@ -26,14 +26,17 @@
export let attachedTo: Ref<Doc>
export let attachedToClass: Ref<Class<Doc>>
export let space: Ref<Space>
export let withRefs = false
const client = getClient()
const dispatch = createEventDispatcher()
const pinnedQuery = createQuery()
const pinnedThreadsQuery = createQuery()
const pinnedRefsQuery = createQuery()
let pinnedMessages: ActivityMessage[] = []
let pinnedThreads: ThreadMessage[] = []
let pinnedRefs: ActivityReference[] = []
$: pinnedQuery.query(
activity.class.ActivityMessage,
@ -51,6 +54,16 @@
}
)
$: if (withRefs) {
pinnedRefsQuery.query(
activity.class.ActivityReference,
{ attachedTo, isPinned: true, space: { $ne: space } },
(res) => {
pinnedRefs = res
}
)
}
$: if (pinnedMessages.length === 0 && pinnedThreads.length === 0) {
dispatch('close', undefined)
}
@ -59,7 +72,10 @@
await client.update(message, { isPinned: false })
}
$: displayMessages = sortActivityMessages(pinnedMessages.concat(pinnedThreads), SortingOrder.Descending)
$: displayMessages = sortActivityMessages(
pinnedMessages.concat(pinnedThreads).concat(pinnedRefs),
SortingOrder.Descending
)
</script>
<div class="antiPopup vScroll popup">

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { Person, PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
import { Class, Doc, getCurrentAccount, Ref, WithLookup } from '@hcengineering/core'
import { Class, Doc, getCurrentAccount, Ref, Space, WithLookup } from '@hcengineering/core'
import { getClient, MessageViewer } from '@hcengineering/presentation'
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
import { getDocLinkTitle } from '@hcengineering/view-resources'
@ -80,7 +80,7 @@
$: person = account?.person !== undefined ? $personByIdStore.get(account.person) : undefined
$: value !== undefined &&
getParentMessage(value.attachedToClass, value.attachedTo).then((res) => {
getParentMessage(value.attachedToClass, value.attachedTo, value.space).then((res) => {
parentMessage = res as DisplayActivityMessage
})
@ -107,9 +107,13 @@
stale = false
}
async function getParentMessage (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<ActivityMessage | undefined> {
async function getParentMessage (
_class: Ref<Class<Doc>>,
_id: Ref<Doc>,
space: Ref<Space>
): Promise<ActivityMessage | undefined> {
if (hierarchy.isDerived(_class, activity.class.ActivityMessage)) {
return await client.findOne<ActivityMessage>(_class, { _id: _id as Ref<ActivityMessage> })
return await client.findOne<ActivityMessage>(_class, { _id: _id as Ref<ActivityMessage>, space })
}
}

View File

@ -27,79 +27,67 @@
import ThreadMessagePreview from '../threads/ThreadMessagePreview.svelte'
export let context: DocNotifyContext
export let object: ChatMessage
const client = getClient()
const hierarchy = client.getHierarchy()
let title: string | undefined = undefined
let parentMessage: ChatMessage | undefined = undefined
let object: Doc | undefined = undefined
let channel: Doc | undefined = undefined
$: isThread = hierarchy.isDerived(context.attachedToClass, chunter.class.ThreadMessage)
$: void client
.findOne(context.attachedToClass as Ref<Class<ChatMessage>>, { _id: context.attachedTo as Ref<ChatMessage> })
.then((res) => {
parentMessage = res
})
$: loadObject(parentMessage, isThread)
$: object &&
getDocLinkTitle(client, object._id, object._class, object).then((res) => {
$: loadChannel(object, isThread)
$: channel &&
getDocLinkTitle(client, channel._id, channel._class, channel).then((res) => {
title = res
})
function loadObject (parentMessage: ChatMessage | undefined, isThread: boolean): void {
if (parentMessage === undefined) {
object = undefined
return
}
const _class = isThread ? (parentMessage as ThreadMessage).objectClass : parentMessage.attachedToClass
const _id = isThread ? (parentMessage as ThreadMessage).objectId : parentMessage.attachedTo
void client.findOne(_class, { _id }).then((res) => {
object = res
function loadChannel (object: ChatMessage, isThread: boolean): void {
const _class = isThread ? (object as ThreadMessage).objectClass : object.attachedToClass
const _id = isThread ? (object as ThreadMessage).objectId : object.attachedTo
console.log({ _class, _id, isThread, object })
void client.findOne(_class, { _id, ...(isThread ? { space: object.space } : {}) }).then((res) => {
channel = res
})
}
function toThread (message: ChatMessage): ThreadMessage {
return message as ThreadMessage
}
function isAvatarIcon (_class: Ref<Class<Doc>>): boolean {
return hierarchy.isDerived(_class, contact.class.Person) || hierarchy.isDerived(_class, chunter.class.DirectMessage)
}
</script>
{#if parentMessage}
<span class="flex-presenter flex-gap-1 font-semi-bold">
{#if isThread || (parentMessage.replies ?? 0) > 0}
<Label label={chunter.string.Thread} />
{:else}
<Label label={chunter.string.Message} />
{/if}
{#if title && object}
<span class="lower">
<Label label={chunter.string.In} />
</span>
{#await getDocTitle(client, object._id, object._class, object) then tooltipLabel}
<span
class="flex-presenter flex-gap-0-5"
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
>
<ObjectIcon
value={object}
size={hierarchy.isDerived(object._class, contact.class.Person) ? 'tiny' : 'small'}
/>
<span class="overflow-label">
{title}
</span>
<span class="flex-presenter flex-gap-1 font-semi-bold">
{#if isThread || (object.replies ?? 0) > 0}
<Label label={chunter.string.Thread} />
{:else}
<Label label={chunter.string.Message} />
{/if}
{#if title && channel}
<span class="lower">
<Label label={chunter.string.In} />
</span>
{#await getDocTitle(client, channel._id, channel._class, channel) then tooltipLabel}
<span
class="flex-presenter flex-gap-0-5"
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
>
<ObjectIcon value={channel} size={isAvatarIcon(channel._class) ? 'tiny' : 'small'} />
<span class="overflow-label">
{title}
</span>
{/await}
{/if}
</span>
<span class="font-normal">
{#if isThread}
<ThreadMessagePreview value={toThread(parentMessage)} readonly type="content-only" />
{:else}
<ChatMessagePreview value={parentMessage} readonly type="content-only" />
{/if}
</span>
{/if}
</span>
{/await}
{/if}
</span>
<span class="font-normal">
{#if isThread}
<ThreadMessagePreview value={toThread(object)} readonly type="content-only" />
{:else}
<ChatMessagePreview value={object} readonly type="content-only" />
{/if}
</span>

View File

@ -20,10 +20,10 @@
DocNotifyContext,
InboxNotification
} from '@hcengineering/notification'
import { getClient } from '@hcengineering/presentation'
import { createQuery, getClient, isSpace, isSpaceClass } from '@hcengineering/presentation'
import { getDocTitle, getDocIdentifier, Menu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
import core, { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
import chunter from '@hcengineering/chunter'
import { personAccountByIdStore } from '@hcengineering/contact-resources'
import { Person, PersonAccount } from '@hcengineering/contact'
@ -47,6 +47,22 @@
const client = getClient()
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
const query = createQuery()
let object: Doc | undefined = undefined
$: query.query(
value.attachedToClass,
{ _id: value.attachedTo, space: isSpaceClass(value.attachedToClass) ? core.space.Space : value.space },
(res) => {
object = res[0]
},
{ limit: 1 }
)
$: if (object?._id !== value.attachedTo) {
object = undefined
}
let isActionMenuOpened = false
let unreadCount = 0
@ -56,13 +72,15 @@
let idTitle: string | undefined
let title: string | undefined
$: void getDocIdentifier(client, value.attachedTo, value.attachedToClass).then((res) => {
idTitle = res
})
$: object &&
getDocIdentifier(client, object._id, object._class, object).then((res) => {
idTitle = res
})
$: void getDocTitle(client, value.attachedTo, value.attachedToClass).then((res) => {
title = res
})
$: object &&
getDocTitle(client, object._id, object._class, object).then((res) => {
title = res
})
$: presenterMixin = hierarchy.classHierarchyMixin(
value.attachedToClass,
@ -184,73 +202,75 @@
dispatch('click', { context: value })
}}
>
<div class="header">
<NotifyContextIcon {value} notifyCount={unreadCount} />
{#if object}
<div class="header">
<NotifyContextIcon {value} notifyCount={unreadCount} {object} />
<div class="labels">
{#if presenterMixin?.labelPresenter}
<Component is={presenterMixin.labelPresenter} props={{ context: value }} />
{:else}
{#if idTitle}
{idTitle}
<div class="labels">
{#if presenterMixin?.labelPresenter}
<Component is={presenterMixin.labelPresenter} props={{ context: value, object }} />
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
{/if}
<span class="title overflow-label clear-mins" {title}>
{#if title}
{title}
{#if idTitle}
{idTitle}
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
{/if}
</span>
{/if}
</div>
<div class="actions clear-mins">
<div class="flex-center">
{#if archivingPromise !== undefined}
<Spinner size="small" />
{:else}
<CheckBox checked={archived} kind="todo" size="medium" on:value={checkContext} />
<span class="title overflow-label clear-mins" {title}>
{#if title}
{title}
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
{/if}
</span>
{/if}
</div>
<ButtonIcon
icon={IconMoreV}
size="small"
kind="tertiary"
inheritColor
pressed={isActionMenuOpened}
on:click={showMenu}
/>
</div>
</div>
<div class="content">
<div class="notifications">
{#each groupedNotifications.slice(0, maxNotifications) as group (getKey(group))}
<div class="notification">
<!-- use:tooltip={canShowTooltip(group)-->
<!-- ? {-->
<!-- component: MessagesPopup,-->
<!-- props: { context: value, notifications: group }-->
<!-- }-->
<!-- : undefined}-->
<div class="embeddedMarker" />
<InboxNotificationPresenter
value={group[0]}
{viewlets}
space={value.space}
on:click={(e) => {
e.preventDefault()
e.stopPropagation()
dispatch('click', { context: value, notification: group[0] })
}}
/>
<div class="actions clear-mins">
<div class="flex-center">
{#if archivingPromise !== undefined}
<Spinner size="small" />
{:else}
<CheckBox checked={archived} kind="todo" size="medium" on:value={checkContext} />
{/if}
</div>
{/each}
<ButtonIcon
icon={IconMoreV}
size="small"
kind="tertiary"
inheritColor
pressed={isActionMenuOpened}
on:click={showMenu}
/>
</div>
</div>
</div>
<div class="content">
<div class="notifications">
{#each groupedNotifications.slice(0, maxNotifications) as group (getKey(group))}
<div class="notification">
<!-- use:tooltip={canShowTooltip(group)-->
<!-- ? {-->
<!-- component: MessagesPopup,-->
<!-- props: { context: value, notifications: group }-->
<!-- }-->
<!-- : undefined}-->
<div class="embeddedMarker" />
<InboxNotificationPresenter
value={group[0]}
{object}
{viewlets}
space={value.space}
on:click={(e) => {
e.preventDefault()
e.stopPropagation()
dispatch('click', { context: value, notification: group[0] })
}}
/>
</div>
{/each}
</div>
</div>
{/if}
</div>
<style lang="scss">
@ -261,6 +281,7 @@
cursor: pointer;
padding: var(--spacing-1_5) var(--spacing-1);
border-bottom: 1px solid var(--global-ui-BorderColor);
min-height: 5.625rem;
.header {
position: relative;

View File

@ -14,9 +14,9 @@
-->
<script lang="ts">
import { DocNotifyContext } from '@hcengineering/notification'
import { Doc } from '@hcengineering/core'
import core, { Doc } from '@hcengineering/core'
import { getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
import { createQuery, getClient } from '@hcengineering/presentation'
import { createQuery, getClient, isSpaceClass } from '@hcengineering/presentation'
import chunter from '@hcengineering/chunter'
import NotifyContextIcon from './NotifyContextIcon.svelte'
@ -27,9 +27,13 @@
let object: Doc | undefined
$: objectQuery.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
object = res[0]
})
$: objectQuery.query(
value.attachedToClass,
{ _id: value.attachedTo, space: isSpaceClass(value.attachedToClass) ? core.space.Space : value.space },
(res) => {
object = res[0]
}
)
async function getTitle (object: Doc) {
if (object._class === chunter.class.DirectMessage) {
@ -41,7 +45,7 @@
{#if object}
<div class="flex-presenter">
<NotifyContextIcon {value} size="small" />
<NotifyContextIcon {value} {object} size="small" />
<div class="mr-4" />
{#await getTitle(object) then title}

View File

@ -15,7 +15,7 @@
<script lang="ts">
import notification, { DocNotifyContext } from '@hcengineering/notification'
import { Component, Icon, IconSize } from '@hcengineering/ui'
import { createQuery, getClient } from '@hcengineering/presentation'
import { getClient } from '@hcengineering/presentation'
import { classIcon } from '@hcengineering/view-resources'
import view from '@hcengineering/view'
import { Doc } from '@hcengineering/core'
@ -25,21 +25,12 @@
export let value: DocNotifyContext
export let size: IconSize = 'medium'
export let notifyCount: number = 0
export let object: Doc | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const query = createQuery()
let object: Doc | undefined = undefined
$: if (object?._id !== value.attachedTo) {
object = undefined
}
$: iconMixin = hierarchy.classHierarchyMixin(value.attachedToClass, view.mixin.ObjectIcon)
$: iconMixin &&
query.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
object = res[0]
})
</script>
<div class="container">

View File

@ -26,6 +26,7 @@
$: void client
.findAll(activity.class.Reaction, {
space: message.space,
_id: { $in: [message.objectId, ...(message?.previousMessages?.map((a) => a.objectId) ?? [])] as Ref<Reaction>[] }
})
.then((res) => {

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { Ref, Space, matchQuery } from '@hcengineering/core'
import { Ref, Space, matchQuery, Doc } from '@hcengineering/core'
import notification, {
ActivityInboxNotification,
ActivityNotificationViewlet,
@ -30,6 +30,7 @@
import { getActions } from '@hcengineering/view-resources'
import { getResource } from '@hcengineering/platform'
export let object: Doc | undefined
export let value: DisplayActivityInboxNotification
export let viewlets: ActivityNotificationViewlet[] = []
export let space: Ref<Space> | undefined = undefined
@ -100,6 +101,6 @@
on:click
/>
{:else}
<ActivityMessagePreview value={displayMessage} {actions} {space} on:click />
<ActivityMessagePreview value={displayMessage} {actions} {space} doc={object} on:click />
{/if}
{/if}

View File

@ -20,6 +20,7 @@
import { ActivityNotificationViewlet, DisplayInboxNotification } from '@hcengineering/notification'
export let value: DisplayInboxNotification
export let object: Doc | undefined
export let viewlets: ActivityNotificationViewlet[] = []
export let space: Ref<Space> | undefined = undefined
@ -30,5 +31,5 @@
</script>
{#if objectPresenter}
<Component is={objectPresenter.presenter} props={{ value, viewlets, space }} on:click />
<Component is={objectPresenter.presenter} props={{ value, viewlets, space, object }} on:click />
{/if}

View File

@ -54,13 +54,13 @@ export async function OnReactionChanged (originTx: Tx, control: TriggerControl):
const txes = await createReactionNotifications(tx, control)
await control.apply(txes, true)
return txes
return []
}
if (innerTx._class === core.class.TxRemoveDoc) {
const txes = await removeReactionNotifications(tx, control)
await control.apply(txes, true)
return txes
return []
}
return []