Merge remote-tracking branch 'origin/develop' into staging

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-09-23 13:36:31 +07:00
commit ad3473acc2
No known key found for this signature in database
GPG Key ID: BD80F68D68D8F7F2
23 changed files with 223 additions and 100 deletions

View File

@ -35,6 +35,7 @@
"@hcengineering/model": "^0.6.11", "@hcengineering/model": "^0.6.11",
"@hcengineering/model-core": "^0.6.0", "@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-preference": "^0.6.0", "@hcengineering/model-preference": "^0.6.0",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/model-view": "^0.6.0", "@hcengineering/model-view": "^0.6.0",
"@hcengineering/notification": "^0.6.23", "@hcengineering/notification": "^0.6.23",
"@hcengineering/platform": "^0.6.11", "@hcengineering/platform": "^0.6.11",

View File

@ -70,6 +70,7 @@ import preference, { TPreference } from '@hcengineering/model-preference'
import view from '@hcengineering/model-view' import view from '@hcengineering/model-view'
import type { Asset, IntlString, Resource } from '@hcengineering/platform' import type { Asset, IntlString, Resource } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui/src/types' import { type AnyComponent } from '@hcengineering/ui/src/types'
import presentation from '@hcengineering/model-presentation'
import activity from './plugin' import activity from './plugin'
import { buildActions } from './actions' import { buildActions } from './actions'
@ -375,6 +376,10 @@ export function createModel (builder: Builder): void {
] ]
}) })
builder.mixin(activity.class.Reaction, core.class.Class, presentation.mixin.InstantTransactions, {
txClasses: [core.class.TxCreateDoc]
})
buildActions(builder) buildActions(builder)
buildNotifications(builder) buildNotifications(builder)
} }

View File

@ -98,7 +98,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
throw new Error('createDoc cannot be called for DOMAIN_MODEL classes with non-model space') throw new Error('createDoc cannot be called for DOMAIN_MODEL classes with non-model space')
} }
const tx = this.txFactory.createTxCreateDoc(_class, space, attributes, id, modifiedOn, modifiedBy) const tx = this.txFactory.createTxCreateDoc(_class, space, attributes, id, modifiedOn, modifiedBy)
await this.client.tx(tx) await this.tx(tx)
return tx.objectId return tx.objectId
} }
@ -122,7 +122,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedOn, modifiedOn,
modifiedBy modifiedBy
) )
await this.client.tx(tx) await this.tx(tx)
return tx.tx.objectId as unknown as Ref<P> return tx.tx.objectId as unknown as Ref<P>
} }
@ -147,7 +147,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedOn, modifiedOn,
modifiedBy modifiedBy
) )
await this.client.tx(tx) await this.tx(tx)
return tx.objectId return tx.objectId
} }
@ -170,7 +170,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedOn, modifiedOn,
modifiedBy modifiedBy
) )
await this.client.tx(tx) await this.tx(tx)
return tx.objectId return tx.objectId
} }
@ -184,7 +184,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedBy?: Ref<Account> modifiedBy?: Ref<Account>
): Promise<TxResult> { ): Promise<TxResult> {
const tx = this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve, modifiedOn, modifiedBy) const tx = this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve, modifiedOn, modifiedBy)
return this.client.tx(tx) return this.tx(tx)
} }
removeDoc<T extends Doc>( removeDoc<T extends Doc>(
@ -195,7 +195,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedBy?: Ref<Account> modifiedBy?: Ref<Account>
): Promise<TxResult> { ): Promise<TxResult> {
const tx = this.txFactory.createTxRemoveDoc(_class, space, objectId, modifiedOn, modifiedBy) const tx = this.txFactory.createTxRemoveDoc(_class, space, objectId, modifiedOn, modifiedBy)
return this.client.tx(tx) return this.tx(tx)
} }
createMixin<D extends Doc, M extends D>( createMixin<D extends Doc, M extends D>(
@ -216,7 +216,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedOn, modifiedOn,
modifiedBy modifiedBy
) )
return this.client.tx(tx) return this.tx(tx)
} }
updateMixin<D extends Doc, M extends D>( updateMixin<D extends Doc, M extends D>(
@ -237,7 +237,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedOn, modifiedOn,
modifiedBy modifiedBy
) )
return this.client.tx(tx) return this.tx(tx)
} }
async update<T extends Doc>( async update<T extends Doc>(

View File

@ -111,7 +111,7 @@ export function addNotification (
title, title,
subTitle, subTitle,
severity, severity,
position: NotificationPosition.TopRight, position: NotificationPosition.BottomLeft,
component, component,
closeTimeout, closeTimeout,
params params

View File

@ -211,6 +211,11 @@
{ {
sort: { sort: {
createdOn: SortingOrder.Ascending createdOn: SortingOrder.Ascending
},
lookup: {
_id: {
reactions: activity.class.Reaction
}
} }
} }
) )

View File

@ -39,7 +39,7 @@
$: void updateInlineActions(message, excludedActions) $: void updateInlineActions(message, excludedActions)
savedMessagesStore.subscribe(() => { savedMessagesStore.subscribe(() => {
void updateInlineActions(message) void updateInlineActions(message, excludedActions)
}) })
function handleActionMenuOpened (): void { function handleActionMenuOpened (): void {

View File

@ -69,6 +69,7 @@
attribute={attributeModel.attribute} attribute={attributeModel.attribute}
value={values[0]} value={values[0]}
{prevValue} {prevValue}
withShowMore={false}
showOnlyDiff showOnlyDiff
/> />
{/if} {/if}

View File

@ -55,7 +55,7 @@
ev.preventDefault() ev.preventDefault()
ev.stopPropagation() ev.stopPropagation()
showPopup(EmojiPopup, {}, ev.target as HTMLElement, async (emoji: string) => { showPopup(EmojiPopup, {}, ev.target as HTMLElement, async (emoji: string) => {
await updateDocReactions(client, reactions, object, emoji) await updateDocReactions(reactions, object, emoji)
}) })
} }
</script> </script>

View File

@ -14,40 +14,48 @@
--> -->
<script lang="ts"> <script lang="ts">
import activity, { ActivityMessage, Reaction } from '@hcengineering/activity' import activity, { ActivityMessage, Reaction } from '@hcengineering/activity'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import { WithLookup } from '@hcengineering/core'
import { getSpace, updateDocReactions } from '../../utils' import { getSpace, updateDocReactions } from '../../utils'
import Reactions from './Reactions.svelte' import Reactions from './Reactions.svelte'
export let object: ActivityMessage | undefined export let object: WithLookup<ActivityMessage> | undefined
export let readonly = false export let readonly = false
const client = getClient()
const reactionsQuery = createQuery() const reactionsQuery = createQuery()
let reactions: Reaction[] = [] let reactions: Reaction[] = []
$: hasReactions = object?.reactions && object.reactions > 0 $: hasReactions = (object?.reactions ?? 0) > 0
$: lookupReactions = object?.$lookup?.reactions as Reaction[] | undefined
$: if (object && hasReactions) { $: updateReactions(hasReactions, object, lookupReactions)
reactionsQuery.query(
activity.class.Reaction, function updateReactions (hasReactions: boolean, object?: ActivityMessage, lookupReaction?: Reaction[]): void {
{ attachedTo: object._id, space: getSpace(object) }, if (lookupReaction !== undefined) {
(res: Reaction[]) => { reactions = lookupReaction
reactions = res } else if (object && hasReactions) {
} reactionsQuery.query(
) activity.class.Reaction,
} else { { attachedTo: object._id, space: getSpace(object) },
reactionsQuery.unsubscribe() (res: Reaction[]) => {
reactions = res
}
)
} else {
reactionsQuery.unsubscribe()
reactions = []
}
} }
const handleClick = (ev: CustomEvent) => { const handleClick = (ev: CustomEvent) => {
if (readonly) return if (readonly) return
void updateDocReactions(client, reactions, object, ev.detail) void updateDocReactions(reactions, object, ev.detail)
} }
</script> </script>
{#if object && hasReactions} {#if object && reactions.length > 0}
<div class="footer flex-col p-inline contrast mt-2 min-h-6"> <div class="footer flex-col p-inline contrast mt-2 min-h-6">
<Reactions {reactions} {object} {readonly} on:click={handleClick} /> <Reactions {reactions} {object} {readonly} on:click={handleClick} />
</div> </div>

View File

@ -66,7 +66,7 @@
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
showPopup(EmojiPopup, {}, e.target as HTMLElement, (emoji: string) => { showPopup(EmojiPopup, {}, e.target as HTMLElement, (emoji: string) => {
void updateDocReactions(client, reactions, message, emoji) void updateDocReactions(reactions, message, emoji)
}) })
} }
</script> </script>

View File

@ -1,12 +1,5 @@
import type { ActivityMessage, Reaction } from '@hcengineering/activity' import type { ActivityMessage, Reaction } from '@hcengineering/activity'
import core, { import core, { getCurrentAccount, isOtherHour, type Doc, type Ref, type Space } from '@hcengineering/core'
getCurrentAccount,
isOtherHour,
type Doc,
type Ref,
type TxOperations,
type Space
} from '@hcengineering/core'
import { getClient, isSpace } from '@hcengineering/presentation' import { getClient, isSpace } from '@hcengineering/presentation'
import { import {
EmojiPopup, EmojiPopup,
@ -22,18 +15,13 @@ import { get } from 'svelte/store'
import { savedMessagesStore } from './activity' import { savedMessagesStore } from './activity'
import activity from './plugin' import activity from './plugin'
export async function updateDocReactions ( export async function updateDocReactions (reactions: Reaction[], object?: Doc, emoji?: string): Promise<void> {
client: TxOperations,
reactions: Reaction[],
object?: Doc,
emoji?: string
): Promise<void> {
if (emoji === undefined || object === undefined) { if (emoji === undefined || object === undefined) {
return return
} }
const client = getClient()
const currentAccount = getCurrentAccount() const currentAccount = getCurrentAccount()
const reaction = reactions.find((r) => r.emoji === emoji && r.createBy === currentAccount._id) const reaction = reactions.find((r) => r.emoji === emoji && r.createBy === currentAccount._id)
if (reaction == null) { if (reaction == null) {
@ -72,7 +60,7 @@ export async function addReactionAction (
closePopup() closePopup()
showPopup(EmojiPopup, {}, element, (emoji: string) => { showPopup(EmojiPopup, {}, element, (emoji: string) => {
void updateDocReactions(client, reactions, message, emoji) void updateDocReactions(reactions, message, emoji)
params?.onClose?.() params?.onClose?.()
}) })
params?.onOpen?.() params?.onOpen?.()

View File

@ -20,6 +20,7 @@ import {
type DocumentQuery, type DocumentQuery,
getCurrentAccount, getCurrentAccount,
isOtherDay, isOtherDay,
type Lookup,
type Ref, type Ref,
SortingOrder, SortingOrder,
type Space, type Space,
@ -119,7 +120,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
}) })
constructor ( constructor (
readonly context: DocNotifyContext | undefined, private context: DocNotifyContext | undefined,
readonly space: Ref<Space>, readonly space: Ref<Space>,
chatId: Ref<Doc>, chatId: Ref<Doc>,
_class: Ref<Class<ActivityMessage>>, _class: Ref<Class<ActivityMessage>>,
@ -209,6 +210,13 @@ export class ChannelDataProvider implements IChannelDataProvider {
) )
} }
async updateNewTimestamp (context?: DocNotifyContext): Promise<void> {
this.context = context ?? this.context
const firstNewMsgIndex = await this.getFirstNewMsgIndex()
const metadata = get(this.metadataStore)
this.newTimestampStore.set(firstNewMsgIndex !== undefined ? metadata[firstNewMsgIndex]?.createdOn : undefined)
}
private async loadInitialMessages ( private async loadInitialMessages (
selectedMsg?: Ref<ActivityMessage>, selectedMsg?: Ref<ActivityMessage>,
loadAll = false, loadAll = false,
@ -285,13 +293,21 @@ export class ChannelDataProvider implements IChannelDataProvider {
}, },
{ {
sort: { createdOn: SortingOrder.Descending }, sort: { createdOn: SortingOrder.Descending },
lookup: { lookup: this.getLookup()
_id: { attachments: attachment.class.Attachment, inlineButtons: chunter.class.InlineButton }
}
} }
) )
} }
getLookup (): Lookup<ActivityMessage> {
return {
_id: {
attachments: attachment.class.Attachment,
inlineButtons: chunter.class.InlineButton,
reactions: activity.class.Reaction
}
}
}
isNextLoading (mode: LoadMode): boolean { isNextLoading (mode: LoadMode): boolean {
return mode === 'forward' ? get(this.isForwardLoading) : get(this.isBackwardLoading) return mode === 'forward' ? get(this.isForwardLoading) : get(this.isBackwardLoading)
} }
@ -331,9 +347,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
{ {
limit: limit ?? this.limit, limit: limit ?? this.limit,
sort: { createdOn: isBackward ? SortingOrder.Descending : SortingOrder.Ascending }, sort: { createdOn: isBackward ? SortingOrder.Descending : SortingOrder.Ascending },
lookup: { lookup: this.getLookup()
_id: { attachments: attachment.class.Attachment, inlineButtons: chunter.class.InlineButton }
}
} }
) )

View File

@ -26,7 +26,7 @@
messageInFocus, messageInFocus,
sortActivityMessages sortActivityMessages
} from '@hcengineering/activity-resources' } from '@hcengineering/activity-resources'
import { Doc, getDay, Ref, Timestamp } from '@hcengineering/core' import { Doc, getCurrentAccount, getDay, Ref, Timestamp } from '@hcengineering/core'
import { DocNotifyContext } from '@hcengineering/notification' import { DocNotifyContext } from '@hcengineering/notification'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { getResource } from '@hcengineering/platform' import { getResource } from '@hcengineering/platform'
@ -72,6 +72,7 @@
const minMsgHeightRem = 2 const minMsgHeightRem = 2
const loadMoreThreshold = 40 const loadMoreThreshold = 40
const me = getCurrentAccount()
const client = getClient() const client = getClient()
const inboxClient = InboxNotificationsClientImpl.getClient() const inboxClient = InboxNotificationsClientImpl.getClient()
const contextByDocStore = inboxClient.contextByDoc const contextByDocStore = inboxClient.contextByDoc
@ -131,8 +132,23 @@
} }
}) })
let isPageHidden = false
let lastMsgBeforeFreeze: Ref<ActivityMessage> | undefined = undefined
function handleVisibilityChange (): void {
if (document.hidden) {
isPageHidden = true
lastMsgBeforeFreeze = shouldScrollToNew ? displayMessages[displayMessages.length - 1]?._id : undefined
} else {
if (isPageHidden) {
isPageHidden = false
void provider.updateNewTimestamp(notifyContext)
}
}
}
function isFreeze (): boolean { function isFreeze (): boolean {
return freeze return freeze || isPageHidden
} }
$: displayMessages = filterChatMessages(messages, filters, filterResources, doc._class, selectedFilters) $: displayMessages = filterChatMessages(messages, filters, filterResources, doc._class, selectedFilters)
@ -295,6 +311,38 @@
} }
} }
function scrollToStartOfNew (): void {
if (!scrollElement || !lastMsgBeforeFreeze) {
return
}
const lastIndex = displayMessages.findIndex(({ _id }) => _id === lastMsgBeforeFreeze)
if (lastIndex === -1) return
const firstNewMessage = displayMessages.find(({ createdBy }, index) => index > lastIndex && createdBy !== me._id)
if (firstNewMessage === undefined) {
scrollToBottom()
return
}
const messagesElements = scrollContentBox?.getElementsByClassName('activityMessage')
const msgElement = messagesElements?.[firstNewMessage._id as any]
if (!msgElement) {
return
}
const messageRect = msgElement.getBoundingClientRect()
const topOffset = messageRect.top - 150
if (topOffset < 0) {
scroller?.scrollBy(topOffset)
} else if (topOffset > 0) {
scroller?.scrollBy(topOffset)
}
}
async function handleScroll ({ autoScrolling }: ScrollParams): Promise<void> { async function handleScroll ({ autoScrolling }: ScrollParams): Promise<void> {
saveScrollPosition() saveScrollPosition()
updateDownButtonVisibility($metadataStore, displayMessages, scrollElement) updateDownButtonVisibility($metadataStore, displayMessages, scrollElement)
@ -339,7 +387,7 @@
return messageRect.top >= containerRect.top && messageRect.bottom - messageRect.height / 2 <= containerRect.bottom return messageRect.top >= containerRect.top && messageRect.bottom - messageRect.height / 2 <= containerRect.bottom
} }
const messagesToReadAccumulator: DisplayActivityMessage[] = [] const messagesToReadAccumulator: Set<DisplayActivityMessage> = new Set<DisplayActivityMessage>()
let messagesToReadAccumulatorTimer: any let messagesToReadAccumulatorTimer: any
function readViewportMessages (): void { function readViewportMessages (): void {
@ -359,13 +407,14 @@
} }
if (messageInView(msgElement, containerRect)) { if (messageInView(msgElement, containerRect)) {
messagesToReadAccumulator.push(message) messagesToReadAccumulator.add(message)
} }
} }
clearTimeout(messagesToReadAccumulatorTimer) clearTimeout(messagesToReadAccumulatorTimer)
messagesToReadAccumulatorTimer = setTimeout(() => { messagesToReadAccumulatorTimer = setTimeout(() => {
const messagesToRead = [...messagesToReadAccumulator] const messagesToRead = [...messagesToReadAccumulator]
messagesToReadAccumulator.clear()
void readChannelMessages(sortActivityMessages(messagesToRead), notifyContext) void readChannelMessages(sortActivityMessages(messagesToRead), notifyContext)
}, 500) }, 500)
} }
@ -555,8 +604,12 @@
return return
} }
const prevCount = messagesCount
messagesCount = newCount
if (isFreeze()) { if (isFreeze()) {
messagesCount = newCount await wait()
scrollToStartOfNew()
return return
} }
@ -565,15 +618,13 @@
} else if (dateToJump !== undefined) { } else if (dateToJump !== undefined) {
await wait() await wait()
scrollToDate(dateToJump) scrollToDate(dateToJump)
} else if (shouldScrollToNew && messagesCount > 0 && newCount > messagesCount) { } else if (shouldScrollToNew && prevCount > 0 && newCount > prevCount) {
await wait() await wait()
scrollToNewMessages() scrollToNewMessages()
} else { } else {
await wait() await wait()
readViewportMessages() readViewportMessages()
} }
messagesCount = newCount
} }
$: void handleMessagesUpdated(displayMessages.length) $: void handleMessagesUpdated(displayMessages.length)
@ -610,10 +661,12 @@
afterUpdate(() => { afterUpdate(() => {
if (!scrollElement) return if (!scrollElement) return
const { scrollHeight } = scrollElement const { offsetHeight, scrollHeight, scrollTop } = scrollElement
if (!isInitialScrolling && prevScrollHeight < scrollHeight && isScrollAtBottom) { if (!isInitialScrolling && !isFreeze() && prevScrollHeight < scrollHeight && isScrollAtBottom) {
scrollToBottom() scrollToBottom()
} else if (isFreeze()) {
isScrollAtBottom = scrollHeight <= Math.ceil(scrollTop + offsetHeight)
} }
}) })
@ -641,10 +694,12 @@
onMount(() => { onMount(() => {
chatReadMessagesStore.update(() => new Set()) chatReadMessagesStore.update(() => new Set())
document.addEventListener('visibilitychange', handleVisibilityChange)
}) })
onDestroy(() => { onDestroy(() => {
unsubscribe() unsubscribe()
document.removeEventListener('visibilitychange', handleVisibilityChange)
}) })
let showScrollDownButton = false let showScrollDownButton = false
@ -715,7 +770,7 @@
const canLoadNextForwardStore = provider.canLoadNextForwardStore const canLoadNextForwardStore = provider.canLoadNextForwardStore
$: if (!freeze) { $: if (!freeze && !isPageHidden && isScrollInitialized) {
readViewportMessages() readViewportMessages()
} }
</script> </script>

View File

@ -37,14 +37,14 @@
$: savedMessages = $savedMessagesStore $: savedMessages = $savedMessagesStore
$: savedAttachments = $savedAttachmentsStore $: savedAttachments = $savedAttachmentsStore
async function openAttachment (attach?: Attachment) { async function openAttachment (attach?: Attachment): Promise<void> {
if (attach === undefined) { if (attach === undefined) {
return return
} }
const messageId: Ref<ActivityMessage> = attach.attachedTo as Ref<ActivityMessage> const messageId: Ref<ActivityMessage> = attach.attachedTo as Ref<ActivityMessage>
await client.findOne(activity.class.ActivityMessage, { _id: messageId }).then((res) => { await client.findOne(activity.class.ActivityMessage, { _id: messageId }).then((res) => {
if (res !== undefined) { if (res !== undefined) {
openMessageFromSpecial(res) void openMessageFromSpecial(res)
} }
}) })
} }
@ -62,8 +62,8 @@
} }
} }
} }
function handleMessageClicked (message?: ActivityMessage) { function handleMessageClicked (message?: ActivityMessage): void {
openMessageFromSpecial(message) void openMessageFromSpecial(message)
} }
</script> </script>

View File

@ -19,4 +19,11 @@
export let message: ActivityMessage export let message: ActivityMessage
</script> </script>
<ActivityMessagePresenter value={message} hideFooter hoverStyles="filledHover" withShowMore={false} skipLabel /> <ActivityMessagePresenter
value={message}
hideFooter
hoverStyles="filledHover"
withShowMore={false}
attachmentImageSize="x-large"
skipLabel
/>

View File

@ -19,6 +19,8 @@
import activity, { ActivityMessage } from '@hcengineering/activity' import activity, { ActivityMessage } from '@hcengineering/activity'
import { PersonAccount } from '@hcengineering/contact' import { PersonAccount } from '@hcengineering/contact'
import { ActivityMessagePresenter } from '@hcengineering/activity-resources' import { ActivityMessagePresenter } from '@hcengineering/activity-resources'
import notification from '@hcengineering/notification'
import attachment from '@hcengineering/attachment'
import chunter from '../../plugin' import chunter from '../../plugin'
import Header from '../Header.svelte' import Header from '../Header.svelte'
@ -32,15 +34,21 @@
$: threadsQuery.query( $: threadsQuery.query(
activity.class.ActivityMessage, activity.class.ActivityMessage,
{ {
replies: { $exists: true } replies: { $exists: true },
[`${notification.mixin.Collaborators}.collaborators`]: me._id
}, },
(res) => { (res) => {
threads = res.filter( threads = res.filter(({ replies }) => (replies ?? 0) > 0)
({ createdBy, repliedPersons, replies }) =>
(replies !== undefined && replies > 0 && createdBy === me._id) || repliedPersons?.includes(me.person)
)
}, },
{ sort: { modifiedOn: SortingOrder.Descending } } {
sort: { modifiedOn: SortingOrder.Descending },
lookup: {
_id: {
attachments: attachment.class.Attachment,
reactions: activity.class.Reaction
}
}
}
) )
</script> </script>

View File

@ -430,11 +430,7 @@ export async function readChannelMessages (
messages: DisplayActivityMessage[], messages: DisplayActivityMessage[],
context: DocNotifyContext | undefined context: DocNotifyContext | undefined
): Promise<void> { ): Promise<void> {
if (messages.length === 0) { if (messages.length === 0 || context === undefined) {
return
}
if (context === undefined) {
return return
} }
@ -442,9 +438,7 @@ export async function readChannelMessages (
const client = getClient().apply(undefined, 'readViewportMessages') const client = getClient().apply(undefined, 'readViewportMessages')
try { try {
const readMessages = get(chatReadMessagesStore) const allIds = getAllIds(messages)
const allIds = getAllIds(messages).filter((id) => !readMessages.has(id))
const notifications = get(inboxClient.activityInboxNotifications) const notifications = get(inboxClient.activityInboxNotifications)
.filter(({ attachedTo, $lookup, isViewed }) => { .filter(({ attachedTo, $lookup, isViewed }) => {
if (isViewed) return false if (isViewed) return false

View File

@ -16,7 +16,12 @@
--> -->
<script lang="ts"> <script lang="ts">
import activity, { ActivityMessage } from '@hcengineering/activity' import activity, { ActivityMessage } from '@hcengineering/activity'
import { ActivityFilter, ActivityMessagePresenter, canGroupMessages } from '@hcengineering/activity-resources' import {
ActivityFilter,
ActivityMessagePresenter,
canGroupMessages,
combineActivityMessages
} from '@hcengineering/activity-resources'
import { SortingOrder } from '@hcengineering/core' import { SortingOrder } from '@hcengineering/core'
import { Document } from '@hcengineering/document' import { Document } from '@hcengineering/document'
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
@ -33,7 +38,9 @@
activity.class.ActivityMessage, activity.class.ActivityMessage,
{ attachedTo: value._id, space: value.space }, { attachedTo: value._id, space: value.space },
(res) => { (res) => {
messages = res void combineActivityMessages(res).then((res) => {
messages = res
})
}, },
{ {
sort: { sort: {

View File

@ -23,6 +23,7 @@
export let value: Markup | undefined export let value: Markup | undefined
export let prevValue: Markup | undefined = undefined export let prevValue: Markup | undefined = undefined
export let attribute: AnyAttribute | undefined = undefined export let attribute: AnyAttribute | undefined = undefined
export let withShowMore = true
export let showOnlyDiff: boolean = false export let showOnlyDiff: boolean = false
@ -85,8 +86,14 @@
} }
</script> </script>
<ShowMore> {#if withShowMore}
<ShowMore>
{#key [value, prevValue]}
<MarkupDiffViewer objectClass={attribute?.attributeOf} {content} {comparedVersion} />
{/key}
</ShowMore>
{:else}
{#key [value, prevValue]} {#key [value, prevValue]}
<MarkupDiffViewer objectClass={attribute?.attributeOf} {content} {comparedVersion} /> <MarkupDiffViewer objectClass={attribute?.attributeOf} {content} {comparedVersion} />
{/key} {/key}
</ShowMore> {/if}

View File

@ -21,7 +21,8 @@
location as locationStore, location as locationStore,
Location, Location,
Header, Header,
Breadcrumbs Breadcrumbs,
getCurrentLocation
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
@ -45,13 +46,33 @@
$: widgetState = widget !== undefined ? $sidebarStore.widgetsState.get(widget._id) : undefined $: widgetState = widget !== undefined ? $sidebarStore.widgetsState.get(widget._id) : undefined
$: tabId = widgetState?.tab $: tabId = widgetState?.tab
$: tabs = widgetState?.tabs ?? [] $: tabs = getTabs(widget, widgetState)
$: tab = tabId !== undefined ? tabs.find((it) => it.id === tabId) ?? tabs[0] : tabs[0] $: tab = tabId !== undefined ? tabs.find((it) => it.id === tabId) ?? tabs[0] : tabs[0]
$: if ($sidebarStore.widget === undefined) { $: if ($sidebarStore.widget === undefined) {
sidebarStore.update((s) => ({ ...s, variant: SidebarVariant.MINI })) sidebarStore.update((s) => ({ ...s, variant: SidebarVariant.MINI }))
} }
function getTabs (widget?: Widget, state?: WidgetState): WidgetTab[] {
if (widget === undefined || !state?.tabs) return []
const loc = getCurrentLocation()
const result: WidgetTab[] = []
for (const tab of state.tabs) {
if (tab.allowedPath !== undefined && !tab.isPinned) {
const path = loc.path.join('/')
if (!path.startsWith(tab.allowedPath)) {
void handleTabClose(tab.id, widget)
continue
}
}
result.push(tab)
}
return result
}
const unsubscribe = locationStore.subscribe((loc: Location) => { const unsubscribe = locationStore.subscribe((loc: Location) => {
if (widget === undefined) return if (widget === undefined) return

View File

@ -248,7 +248,7 @@ export async function getPersonNotificationTxes (
...content, ...content,
docNotifyContext: context._id, docNotifyContext: context._id,
_id: generateId(), _id: generateId(),
_class: notification.class.CommonInboxNotification, _class: notification.class.MentionInboxNotification,
space: receiverSpace._id, space: receiverSpace._id,
modifiedOn: originTx.modifiedOn, modifiedOn: originTx.modifiedOn,
modifiedBy: sender._id modifiedBy: sender._id

View File

@ -78,7 +78,7 @@ import serverNotification, {
SenderInfo SenderInfo
} from '@hcengineering/server-notification' } from '@hcengineering/server-notification'
import serverView from '@hcengineering/server-view' import serverView from '@hcengineering/server-view'
import { markupToHTML, markupToText, stripTags } from '@hcengineering/text' import { markupToText, stripTags } from '@hcengineering/text'
import { encodeObjectURI } from '@hcengineering/view' import { encodeObjectURI } from '@hcengineering/view'
import { workbenchId } from '@hcengineering/workbench' import { workbenchId } from '@hcengineering/workbench'
import webpush, { WebPushError } from 'web-push' import webpush, { WebPushError } from 'web-push'
@ -240,8 +240,10 @@ export async function getContentByTemplate (
notificationData !== undefined && notificationData !== undefined &&
control.hierarchy.isDerived(notificationData._class, notification.class.MentionInboxNotification) control.hierarchy.isDerived(notificationData._class, notification.class.MentionInboxNotification)
) { ) {
const text = (notificationData as MentionInboxNotification).messageHtml const messageContent = (notificationData as MentionInboxNotification).messageHtml
params.body = text !== undefined ? markupToHTML(text) : params.body const text = messageContent !== undefined ? markupToText(messageContent) : undefined
params.body = text ?? params.body
params.message = text ?? params.message
} }
if (message !== undefined) { if (message !== undefined) {

View File

@ -698,16 +698,16 @@ export class PlatformWorker {
errors++ errors++
return return
} }
if (workspaceInfo?.workspace === undefined) {
this.ctx.error('No workspace exists for workspaceId', { workspace })
errors++
return
}
if (workspaceInfo?.disabled === true) {
this.ctx.error('Workspace is disabled workspaceId', { workspace })
return
}
try { try {
if (workspaceInfo?.workspace === undefined) {
this.ctx.error('No workspace exists for workspaceId', { workspace })
errors++
return
}
if (workspaceInfo?.disabled === true) {
this.ctx.error('Workspace is disabled workspaceId', { workspace })
return
}
const branding = Object.values(this.brandingMap).find((b) => b.key === workspaceInfo?.branding) ?? null const branding = Object.values(this.brandingMap).find((b) => b.key === workspaceInfo?.branding) ?? null
const workerCtx = this.ctx.newChild('worker', { workspace: workspaceInfo.workspace }, {}) const workerCtx = this.ctx.newChild('worker', { workspace: workspaceInfo.workspace }, {})