UBERF-5686: Fix copy link (#5368)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-04-16 13:11:21 +04:00 committed by GitHub
parent 0d1d1a8b8d
commit d81f91fc3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 396 additions and 453 deletions

View File

@ -402,29 +402,28 @@ export function createModel (builder: Builder, options = { addApplication: true
encode: chunter.function.GetThreadLink encode: chunter.function.GetThreadLink
}) })
// Note: it is not working now, need to fix navigation by url UBERF-5686 createAction(
// createAction( builder,
// builder, {
// { action: view.actionImpl.CopyTextToClipboard,
// action: view.actionImpl.CopyTextToClipboard, actionProps: {
// actionProps: { textProvider: chunter.function.GetLink
// textProvider: chunter.function.GetLink },
// }, label: chunter.string.CopyLink,
// label: chunter.string.CopyLink, icon: chunter.icon.Copy,
// icon: chunter.icon.Copy, keyBinding: [],
// keyBinding: [], input: 'none',
// input: 'none', category: chunter.category.Chunter,
// category: chunter.category.Chunter, target: activity.class.ActivityMessage,
// target: activity.class.ActivityMessage, visibilityTester: chunter.function.CanCopyMessageLink,
// visibilityTester: chunter.function.CanCopyMessageLink, context: {
// context: { mode: ['context', 'browser'],
// mode: ['context', 'browser'], application: chunter.app.Chunter,
// application: chunter.app.Chunter, group: 'copy'
// group: 'copy' }
// } },
// }, chunter.action.CopyChatMessageLink
// chunter.action.CopyChatMessageLink )
// )
builder.mixin(chunter.class.ChunterMessage, core.class.Class, view.mixin.ClassFilters, { builder.mixin(chunter.class.ChunterMessage, core.class.Class, view.mixin.ClassFilters, {
filters: ['space', '_class'] filters: ['space', '_class']
@ -602,20 +601,6 @@ export function createModel (builder: Builder, options = { addApplication: true
chunter.action.LeaveChannel chunter.action.LeaveChannel
) )
createAction(
builder,
{
...viewTemplates.open,
target: notification.class.DocNotifyContext,
context: {
mode: ['browser', 'context'],
group: 'create'
},
action: chunter.actionImpl.OpenChannel
},
chunter.action.OpenChannel
)
createAction(builder, { createAction(builder, {
...notificationActionTemplates.pinContext, ...notificationActionTemplates.pinContext,
label: chunter.string.StarChannel, label: chunter.string.StarChannel,

View File

@ -24,8 +24,8 @@
import ChannelScrollView from './ChannelScrollView.svelte' import ChannelScrollView from './ChannelScrollView.svelte'
import { ChannelDataProvider } from '../channelDataProvider' import { ChannelDataProvider } from '../channelDataProvider'
export let context: DocNotifyContext export let object: Doc
export let object: Doc | undefined export let context: DocNotifyContext | undefined
export let filters: Ref<ActivityMessagesFilter>[] = [] export let filters: Ref<ActivityMessagesFilter>[] = []
export let isAsideOpened = false export let isAsideOpened = false
@ -39,11 +39,11 @@
selectedMessageId = getMessageFromLoc(newLocation) selectedMessageId = getMessageFromLoc(newLocation)
}) })
$: isDocChannel = !hierarchy.isDerived(context.attachedToClass, chunter.class.ChunterSpace) $: isDocChannel = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
$: _class = isDocChannel ? activity.class.ActivityMessage : chunter.class.ChatMessage $: _class = isDocChannel ? activity.class.ActivityMessage : chunter.class.ChatMessage
$: collection = isDocChannel ? 'comments' : 'messages' $: collection = isDocChannel ? 'comments' : 'messages'
$: updateDataProvider(context.attachedTo, _class, context.lastViewedTimestamp, selectedMessageId) $: updateDataProvider(object._id, _class, context?.lastViewedTimestamp, selectedMessageId)
function updateDataProvider ( function updateDataProvider (
attachedTo: Ref<Doc>, attachedTo: Ref<Doc>,
@ -62,8 +62,8 @@
{#if dataProvider} {#if dataProvider}
<ChannelScrollView <ChannelScrollView
objectId={context.attachedTo} objectId={object._id}
objectClass={context.attachedToClass} objectClass={object._class}
{object} {object}
skipLabels={!isDocChannel} skipLabels={!isDocChannel}
selectedFilters={filters} selectedFilters={filters}

View File

@ -36,7 +36,7 @@
}) })
</script> </script>
{#if context} {#if object}
<div class="antiComponent"> <div class="antiComponent">
<ChannelView {object} {context} embedded allowClose on:close /> <ChannelView {object} {context} embedded allowClose on:close />
</div> </div>

View File

@ -26,8 +26,8 @@
import chunter from '../plugin' import chunter from '../plugin'
import ChannelAside from './chat/ChannelAside.svelte' import ChannelAside from './chat/ChannelAside.svelte'
export let context: DocNotifyContext export let object: Doc
export let object: Doc | undefined = undefined export let context: DocNotifyContext | undefined
export let allowClose = false export let allowClose = false
export let embedded = false export let embedded = false
@ -43,9 +43,8 @@
isThreadOpened = newLocation.path[4] != null isThreadOpened = newLocation.path[4] != null
}) })
$: isDocChat = !hierarchy.isDerived(context.attachedToClass, chunter.class.ChunterSpace) $: isDocChat = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
$: withAside = $: withAside = !embedded && !isThreadOpened && !hierarchy.isDerived(object._class, chunter.class.DirectMessage)
!embedded && !isThreadOpened && !hierarchy.isDerived(context.attachedToClass, chunter.class.DirectMessage)
function toChannel (object?: Doc): Channel | undefined { function toChannel (object?: Doc): Channel | undefined {
return object as Channel | undefined return object as Channel | undefined
@ -56,8 +55,8 @@
<div class="popupPanel panel" class:embedded> <div class="popupPanel panel" class:embedded>
<ChannelHeader <ChannelHeader
_id={context.attachedTo} _id={object._id}
_class={context.attachedToClass} _class={object._class}
{object} {object}
{allowClose} {allowClose}
{withAside} {withAside}
@ -72,7 +71,7 @@
<div class="popupPanel-body" class:asideShown={withAside && isAsideShown}> <div class="popupPanel-body" class:asideShown={withAside && isAsideShown}>
<div class="popupPanel-body__main"> <div class="popupPanel-body__main">
{#key context._id} {#key object._id}
<ChannelComponent {context} {object} {filters} isAsideOpened={(withAside && isAsideShown) || isThreadOpened} /> <ChannelComponent {context} {object} {filters} isAsideOpened={(withAside && isAsideShown) || isThreadOpened} />
{/key} {/key}
</div> </div>
@ -82,10 +81,10 @@
<div class="popupPanel-body__aside" class:float={false} class:shown={withAside && isAsideShown}> <div class="popupPanel-body__aside" class:float={false} class:shown={withAside && isAsideShown}>
<Separator name="aside" float index={0} /> <Separator name="aside" float index={0} />
<div class="antiPanel-wrap__content"> <div class="antiPanel-wrap__content">
{#if hierarchy.isDerived(context.attachedToClass, chunter.class.Channel)} {#if hierarchy.isDerived(object._class, chunter.class.Channel)}
<ChannelAside _class={context.attachedToClass} object={toChannel(object)} /> <ChannelAside _class={object._class} object={toChannel(object)} />
{:else} {:else}
<DocAside _class={context.attachedToClass} {object} /> <DocAside _class={object._class} {object} />
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -21,9 +21,11 @@
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import { SearchEdit } from '@hcengineering/ui' import { SearchEdit } from '@hcengineering/ui'
import { openDoc } from '@hcengineering/view-resources' import { openDoc } from '@hcengineering/view-resources'
import { userSearch } from '../index' import { userSearch } from '../index'
import chunter from '../plugin' import chunter from '../plugin'
import { getDmName, navigateToSpecial } from '../utils' import { getDmName } from '../utils'
import { navigateToSpecial } from '../navigation'
export let spaceId: Ref<DirectMessage> | undefined export let spaceId: Ref<DirectMessage> | undefined
export let withSearch: boolean = true export let withSearch: boolean = true

View File

@ -32,7 +32,7 @@
import { ActivityMessagesFilter } from '@hcengineering/activity' import { ActivityMessagesFilter } from '@hcengineering/activity'
import { userSearch } from '../index' import { userSearch } from '../index'
import { navigateToSpecial } from '../utils' import { navigateToSpecial } from '../navigation'
import ChannelMessagesFilter from './ChannelMessagesFilter.svelte' import ChannelMessagesFilter from './ChannelMessagesFilter.svelte'
export let object: Doc | undefined = undefined export let object: Doc | undefined = undefined

View File

@ -25,9 +25,8 @@
} from '@hcengineering/notification' } from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform' import { getResource } from '@hcengineering/platform'
import { Label, TimeSince, getLocation, navigate } from '@hcengineering/ui' import { Label, TimeSince, getLocation, navigate } from '@hcengineering/ui'
import { get } from 'svelte/store'
import { buildThreadLink } from '../utils' import { buildThreadLink } from '../navigation'
export let object: ActivityMessage export let object: ActivityMessage
export let embedded = false export let embedded = false
@ -71,14 +70,14 @@
.some(({ isViewed }) => !isViewed) .some(({ isViewed }) => !isViewed)
} }
function updateQuery (personIds: Set<Ref<Person>>, personById: IdMap<Person>) { function updateQuery (personIds: Set<Ref<Person>>, personById: IdMap<Person>): void {
displayPersons = Array.from(personIds) displayPersons = Array.from(personIds)
.map((id) => personById.get(id)) .map((id) => personById.get(id))
.filter((person): person is Person => person !== undefined) .filter((person): person is Person => person !== undefined)
.slice(0, maxDisplayPersons - 1) .slice(0, maxDisplayPersons - 1)
} }
function handleReply (e: any) { function handleReply (e: MouseEvent): void {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
@ -87,17 +86,7 @@
return return
} }
if (inboxClient === undefined) { navigate(buildThreadLink(getLocation(), object.attachedTo, object.attachedToClass, object._id))
return
}
const context = get(inboxClient.contextByDoc).get(object.attachedTo)
if (context === undefined) {
return
}
navigate(buildThreadLink(getLocation(), context._id, object._id))
} }
</script> </script>

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Doc, IdMap, Ref } from '@hcengineering/core' import { Doc, Ref, Class } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import { import {
Component, Component,
@ -24,7 +24,6 @@
Separator, Separator,
Location Location
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { DocNotifyContext } from '@hcengineering/notification'
import { NavigatorModel, SpecialNavModel } from '@hcengineering/workbench' import { NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
@ -33,13 +32,15 @@
import ChatNavigator from './navigator/ChatNavigator.svelte' import ChatNavigator from './navigator/ChatNavigator.svelte'
import ChannelView from '../ChannelView.svelte' import ChannelView from '../ChannelView.svelte'
import { chatSpecials, loadSavedAttachments } from './utils' import { chatSpecials, loadSavedAttachments } from './utils'
import { SelectChannelEvent } from './types'
import { decodeChannelURI, openChannel } from '../../navigation'
export let visibleNav: boolean = true export let visibleNav: boolean = true
export let navFloat: boolean = false export let navFloat: boolean = false
export let appsDirection: 'vertical' | 'horizontal' = 'horizontal' export let appsDirection: 'vertical' | 'horizontal' = 'horizontal'
const notificationsClient = InboxNotificationsClientImpl.getClient() const notificationsClient = InboxNotificationsClientImpl.getClient()
const contextByIdStore = notificationsClient.contextById const contextByDocStore = notificationsClient.contextByDoc
const objectQuery = createQuery() const objectQuery = createQuery()
const navigatorModel: NavigatorModel = { const navigatorModel: NavigatorModel = {
@ -47,8 +48,7 @@
specials: chatSpecials specials: chatSpecials
} }
let selectedContextId: Ref<DocNotifyContext> | undefined = undefined let selectedData: { _id: Ref<Doc>, _class: Ref<Class<Doc>> } | undefined = undefined
let selectedContext: DocNotifyContext | undefined = undefined
let currentSpecial: SpecialNavModel | undefined let currentSpecial: SpecialNavModel | undefined
@ -58,17 +58,24 @@
syncLocation(loc) syncLocation(loc)
}) })
$: updateSelectedContext($contextByIdStore, selectedContextId) $: void loadObject(selectedData?._id, selectedData?._class)
async function loadObject (_id?: Ref<Doc>, _class?: Ref<Class<Doc>>): Promise<void> {
if (_id === undefined || _class === undefined) {
object = undefined
objectQuery.unsubscribe()
return
}
$: selectedContext &&
objectQuery.query( objectQuery.query(
selectedContext.attachedToClass, _class,
{ _id: selectedContext.attachedTo }, { _id },
(res) => { (res) => {
object = res[0] object = res[0]
}, },
{ limit: 1 } { limit: 1 }
) )
}
function syncLocation (loc: Location) { function syncLocation (loc: Location) {
const specialId = loc.path[3] const specialId = loc.path[3]
@ -76,39 +83,24 @@
currentSpecial = navigatorModel?.specials?.find((special) => special.id === specialId) currentSpecial = navigatorModel?.specials?.find((special) => special.id === specialId)
if (currentSpecial !== undefined) { if (currentSpecial !== undefined) {
selectedContext = undefined selectedData = undefined
selectedContextId = undefined
} else { } else {
selectedContextId = loc.path[3] as Ref<DocNotifyContext> | undefined const [_id, _class] = decodeChannelURI(loc.path[3])
selectedData = { _id, _class }
} }
} }
function updateSelectedContext (contexts: IdMap<DocNotifyContext>, _id?: Ref<DocNotifyContext>) { function handleChannelSelected (event: CustomEvent): void {
if (selectedContextId === undefined) { const detail = (event.detail ?? {}) as SelectChannelEvent
selectedContext = undefined
} else {
selectedContext = contexts.get(selectedContextId)
}
}
function handleChannelSelected (event: CustomEvent) { selectedData = { _id: detail.object._id, _class: detail.object._class }
const { context } = event.detail ?? {}
selectedContext = context if (selectedData._id !== object?._id) {
selectedContextId = selectedContext?._id object = detail.object
if (selectedContext?.attachedTo !== object?._id) {
object = undefined
} }
const loc = getCurrentLocation() openChannel(selectedData._id, selectedData._class)
loc.path[3] = selectedContextId as string
loc.path[4] = ''
loc.query = { ...loc.query, message: null }
loc.path.length = 4
navigate(loc)
} }
defineSeparators('chat', [ defineSeparators('chat', [
@ -127,7 +119,7 @@
class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'} background-surface-color" class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'} background-surface-color"
> >
<div class="antiPanel-wrap__content"> <div class="antiPanel-wrap__content">
<ChatNavigator {selectedContextId} {currentSpecial} on:select={handleChannelSelected} /> <ChatNavigator objectId={selectedData?._id} {object} {currentSpecial} on:select={handleChannelSelected} />
</div> </div>
<Separator name="chat" float={navFloat ? 'navigator' : true} index={0} /> <Separator name="chat" float={navFloat ? 'navigator' : true} index={0} />
</div> </div>
@ -153,8 +145,9 @@
} }
}} }}
/> />
{:else if selectedContext} {:else if object}
<ChannelView context={selectedContext} {object} /> {@const context = $contextByDocStore.get(object._id)}
<ChannelView {object} {context} />
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -43,6 +43,6 @@
{#if threadId} {#if threadId}
<ThreadView _id={threadId} on:close /> <ThreadView _id={threadId} on:close />
{:else if context} {:else if object}
<ChannelView {object} {context} allowClose embedded on:close /> <ChannelView {object} {context} allowClose embedded on:close />
{/if} {/if}

View File

@ -21,7 +21,7 @@
import Lock from '../../icons/Lock.svelte' import Lock from '../../icons/Lock.svelte'
import chunter from '../../../plugin' import chunter from '../../../plugin'
import { openChannel } from '../../../index' import { openChannel } from '../../../navigation'
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
@ -60,14 +60,14 @@
members: [accountId], members: [accountId],
topic: description topic: description
}) })
const notifyContextId = await client.createDoc(notification.class.DocNotifyContext, core.space.Space, { await client.createDoc(notification.class.DocNotifyContext, core.space.Space, {
user: accountId, user: accountId,
attachedTo: channelId, attachedTo: channelId,
attachedToClass: chunter.class.Channel, attachedToClass: chunter.class.Channel,
hidden: false hidden: false
}) })
await openChannel(undefined, undefined, { _id: notifyContextId }) openChannel(channelId, chunter.class.Channel)
} }
function handleCancel (): void { function handleCancel (): void {

View File

@ -27,7 +27,7 @@
import chunter from '../../../plugin' import chunter from '../../../plugin'
import { buildDmName } from '../../../utils' import { buildDmName } from '../../../utils'
import ChannelMembers from '../../ChannelMembers.svelte' import ChannelMembers from '../../ChannelMembers.svelte'
import { openChannel } from '../../../index' import { openChannel } from '../../../navigation'
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
@ -64,21 +64,6 @@
} }
} }
const context = direct
? await client.findOne(notification.class.DocNotifyContext, {
user: myAccId,
attachedTo: direct._id,
attachedToClass: chunter.class.DirectMessage
})
: undefined
if (context !== undefined) {
await client.diffUpdate(context, { hidden: false })
await openChannel(context)
return
}
const dmId = const dmId =
direct?._id ?? direct?._id ??
(await client.createDoc(chunter.class.DirectMessage, core.space.Space, { (await client.createDoc(chunter.class.DirectMessage, core.space.Space, {
@ -89,14 +74,27 @@
members: accIds members: accIds
})) }))
const notifyContextId = await client.createDoc(notification.class.DocNotifyContext, core.space.Space, { const context = await client.findOne(notification.class.DocNotifyContext, {
user: myAccId,
attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage
})
if (context !== undefined) {
await client.diffUpdate(context, { hidden: false })
openChannel(dmId, chunter.class.DirectMessage)
return
}
await client.createDoc(notification.class.DocNotifyContext, core.space.Space, {
user: myAccId, user: myAccId,
attachedTo: dmId, attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage, attachedToClass: chunter.class.DirectMessage,
hidden: false hidden: false
}) })
await openChannel(undefined, undefined, { _id: notifyContextId }) openChannel(dmId, chunter.class.DirectMessage)
} }
function handleCancel (): void { function handleCancel (): void {

View File

@ -20,11 +20,12 @@
import { translate } from '@hcengineering/platform' import { translate } from '@hcengineering/platform'
import { Action } from '@hcengineering/ui' import { Action } from '@hcengineering/ui'
import { ChatNavGroupModel } from '../types' import { ChatGroup, ChatNavGroupModel } from '../types'
import ChatNavSection from './ChatNavSection.svelte' import ChatNavSection from './ChatNavSection.svelte'
import chunter from '../../../plugin' import chunter from '../../../plugin'
export let selectedContextId: Ref<DocNotifyContext> | undefined = undefined export let objectId: Ref<Doc> | undefined
export let object: Doc | undefined
export let model: ChatNavGroupModel export let model: ChatNavGroupModel
interface Section { interface Section {
@ -43,6 +44,8 @@
let objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>() let objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>()
let contexts: DocNotifyContext[] = [] let contexts: DocNotifyContext[] = []
let shouldPushObject = false
let sections: Section[] = [] let sections: Section[] = []
$: contextsQuery.query( $: contextsQuery.query(
@ -63,10 +66,15 @@
$: loadObjects(contexts) $: loadObjects(contexts)
$: void getSections(objectsByClass, model).then((res) => { $: void getSections(objectsByClass, model, shouldPushObject ? object : undefined).then((res) => {
sections = res sections = res
}) })
$: shouldPushObject =
object !== undefined &&
getObjectGroup(object) === model.id &&
!contexts.some(({ attachedTo }) => attachedTo === object?._id)
function loadObjects (contexts: DocNotifyContext[]): void { function loadObjects (contexts: DocNotifyContext[]): void {
const contextsByClass = groupByArray(contexts, ({ attachedToClass }) => attachedToClass) const contextsByClass = groupByArray(contexts, ({ attachedToClass }) => attachedToClass)
@ -92,9 +100,22 @@
} }
} }
function getObjectGroup (object: Doc): ChatGroup {
if (hierarchy.isDerived(object._class, chunter.class.Channel)) {
return 'channels'
}
if (hierarchy.isDerived(object._class, chunter.class.DirectMessage)) {
return 'direct'
}
return 'activity'
}
async function getSections ( async function getSections (
objectsByClass: Map<Ref<Class<Doc>>, Doc[]>, objectsByClass: Map<Ref<Class<Doc>>, Doc[]>,
model: ChatNavGroupModel model: ChatNavGroupModel,
object: Doc | undefined
): Promise<Section[]> { ): Promise<Section[]> {
const result: Section[] = [] const result: Section[] = []
@ -108,13 +129,40 @@
return result return result
} }
let isObjectPushed = false
if (
Array.from(objectsByClass.values())
.flat()
.some((o) => o._id === object?._id)
) {
isObjectPushed = true
}
for (const [_class, objects] of objectsByClass.entries()) { for (const [_class, objects] of objectsByClass.entries()) {
const clazz = hierarchy.getClass(_class) const clazz = hierarchy.getClass(_class)
const sectionObjects = [...objects]
if (object && _class === object._class && !objects.some(({ _id }) => _id === object._id)) {
isObjectPushed = true
sectionObjects.push(object)
}
result.push({ result.push({
id: _class, id: _class,
_class, _class,
objects, objects: sectionObjects,
label: await translate(clazz.pluralLabel ?? clazz.label, {})
})
}
if (!isObjectPushed && object) {
const clazz = hierarchy.getClass(object._class)
result.push({
id: object._id,
_class: object._class,
objects: [object],
label: await translate(clazz.pluralLabel ?? clazz.label, {}) label: await translate(clazz.pluralLabel ?? clazz.label, {})
}) })
} }
@ -141,7 +189,7 @@
<ChatNavSection <ChatNavSection
objects={section.objects} objects={section.objects}
{contexts} {contexts}
{selectedContextId} {objectId}
header={section.label} header={section.label}
actions={getSectionActions(section, contexts)} actions={getSectionActions(section, contexts)}
sortFn={model.sortFn} sortFn={model.sortFn}

View File

@ -25,11 +25,14 @@
isMentionNotification isMentionNotification
} from '@hcengineering/notification-resources' } from '@hcengineering/notification-resources'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import view from '@hcengineering/view'
import { Doc } from '@hcengineering/core'
import NavItem from './NavItem.svelte' import NavItem from './NavItem.svelte'
import { ChatNavItemModel } from '../types' import { ChatNavItemModel } from '../types'
import { openChannel } from '../../../navigation'
export let context: DocNotifyContext export let context: DocNotifyContext | undefined
export let item: ChatNavItemModel export let item: ChatNavItemModel
export let isSelected = false export let isSelected = false
@ -43,6 +46,9 @@
let actions: Action[] = [] let actions: Action[] = []
notificationClient.inboxNotificationsByContext.subscribe((res) => { notificationClient.inboxNotificationsByContext.subscribe((res) => {
if (context === undefined) {
return
}
notifications = (res.get(context._id) ?? []).filter((n) => isMentionNotification(n) || isActivityNotification(n)) notifications = (res.get(context._id) ?? []).filter((n) => isMentionNotification(n) || isActivityNotification(n))
}) })
@ -50,12 +56,25 @@
notificationsCount = res notificationsCount = res
}) })
$: void getChannelActions(context).then((res) => { $: void getChannelActions(context, item.object).then((res) => {
actions = res actions = res
}) })
async function getChannelActions (context: DocNotifyContext): Promise<Action[]> { async function getChannelActions (context: DocNotifyContext | undefined, object: Doc): Promise<Action[]> {
const result = [] const result: Action[] = []
if (context === undefined) {
return []
}
result.push({
icon: view.icon.Open,
label: view.string.Open,
action: async () => {
openChannel(object._id, object._class)
}
})
const excludedActions = [ const excludedActions = [
notification.action.DeleteContextNotifications, notification.action.DeleteContextNotifications,
notification.action.UnReadNotifyContext, notification.action.UnReadNotifyContext,
@ -100,6 +119,6 @@
description={item.description} description={item.description}
{actions} {actions}
on:click={() => { on:click={() => {
dispatch('select', { doc: item.object, context }) dispatch('select', { object: item.object })
}} }}
/> />

View File

@ -33,7 +33,7 @@
export let contexts: DocNotifyContext[] export let contexts: DocNotifyContext[]
export let actions: Action[] = [] export let actions: Action[] = []
export let maxItems: number | undefined = undefined export let maxItems: number | undefined = undefined
export let selectedContextId: Ref<DocNotifyContext> | undefined = undefined export let objectId: Ref<Doc> | undefined
export let sortFn: (items: ChatNavItemModel[], contexts: DocNotifyContext[]) => ChatNavItemModel[] export let sortFn: (items: ChatNavItemModel[], contexts: DocNotifyContext[]) => ChatNavItemModel[]
const client = getClient() const client = getClient()
@ -52,7 +52,7 @@
$: canShowMore = !!maxItems && items.length > maxItems $: canShowMore = !!maxItems && items.length > maxItems
$: visibleItems = getVisibleItems(canShowMore, isShownMore, maxItems, items, selectedContextId, contexts) $: visibleItems = getVisibleItems(canShowMore, isShownMore, maxItems, items, objectId)
async function getChatNavItems (objects: Doc[]): Promise<ChatNavItemModel[]> { async function getChatNavItems (objects: Doc[]): Promise<ChatNavItemModel[]> {
const items: ChatNavItemModel[] = [] const items: ChatNavItemModel[] = []
@ -98,8 +98,7 @@
isShownMore: boolean, isShownMore: boolean,
maxItems: number | undefined, maxItems: number | undefined,
items: ChatNavItemModel[], items: ChatNavItemModel[],
selectedContextId: Ref<DocNotifyContext> | undefined, selectedObjectId: Ref<Doc> | undefined
contexts: DocNotifyContext[]
): ChatNavItemModel[] { ): ChatNavItemModel[] {
if (!canShowMore || isShownMore) { if (!canShowMore || isShownMore) {
return items return items
@ -107,23 +106,17 @@
const result = items.slice(0, maxItems) const result = items.slice(0, maxItems)
if (selectedContextId === undefined) { if (selectedObjectId === undefined) {
return result return result
} }
const context = contexts.find(({ _id }) => _id === selectedContextId) const exists = result.some(({ id }) => id === selectedObjectId)
if (context === undefined) {
return result
}
const exists = result.some(({ id }) => id === context.attachedTo)
if (exists) { if (exists) {
return result return result
} }
const selectedItem = items.find(({ id }) => id === context?.attachedTo) const selectedItem = items.find(({ id }) => id === selectedObjectId)
if (selectedItem === undefined) { if (selectedItem === undefined) {
return result return result
@ -148,9 +141,7 @@
{#if !isCollapsed} {#if !isCollapsed}
{#each visibleItems as item (item.id)} {#each visibleItems as item (item.id)}
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)} {@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
{#if context} <ChatNavItem {context} isSelected={objectId === item.id} {item} on:select />
<ChatNavItem {context} isSelected={selectedContextId === context._id} {item} on:select />
{/if}
{/each} {/each}
{#if canShowMore} {#if canShowMore}
<div class="showMore"> <div class="showMore">

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Ref } from '@hcengineering/core' import { Doc, Ref } from '@hcengineering/core'
import { Scroller, SearchEdit, Label, Button, IconAdd, showPopup, Menu } from '@hcengineering/ui' import { Scroller, SearchEdit, Label, Button, IconAdd, showPopup, Menu } from '@hcengineering/ui'
import { DocNotifyContext } from '@hcengineering/notification' import { DocNotifyContext } from '@hcengineering/notification'
import { SpecialNavModel } from '@hcengineering/workbench' import { SpecialNavModel } from '@hcengineering/workbench'
@ -27,9 +27,10 @@
import { chatNavGroupModels, chatSpecials } from '../utils' import { chatNavGroupModels, chatSpecials } from '../utils'
import ChatSpecialElement from './ChatSpecialElement.svelte' import ChatSpecialElement from './ChatSpecialElement.svelte'
import { userSearch } from '../../../index' import { userSearch } from '../../../index'
import { navigateToSpecial } from '../../../utils' import { navigateToSpecial } from '../../../navigation'
export let selectedContextId: Ref<DocNotifyContext> | undefined export let objectId: Ref<Doc> | undefined
export let object: Doc | undefined
export let currentSpecial: SpecialNavModel | undefined export let currentSpecial: SpecialNavModel | undefined
const notificationClient = InboxNotificationsClientImpl.getClient() const notificationClient = InboxNotificationsClientImpl.getClient()
@ -105,7 +106,7 @@
</div> </div>
<Scroller> <Scroller>
{#each chatNavGroupModels as model} {#each chatNavGroupModels as model}
<ChatNavGroup {selectedContextId} {model} on:select /> <ChatNavGroup {object} {objectId} {model} on:select />
{/each} {/each}
<div class="antiNav-space" /> <div class="antiNav-space" />
</Scroller> </Scroller>

View File

@ -24,24 +24,12 @@
Space Space
} from '@hcengineering/core' } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery } from '@hcengineering/presentation'
import { import { AnyComponent, Button, Icon, Label, Scroller, SearchEdit, showPopup } from '@hcengineering/ui'
AnyComponent,
Button,
getCurrentResolvedLocation,
Icon,
Label,
navigate,
Scroller,
SearchEdit,
showPopup
} from '@hcengineering/ui'
import { FilterBar, FilterButton, SpacePresenter } from '@hcengineering/view-resources' import { FilterBar, FilterButton, SpacePresenter } from '@hcengineering/view-resources'
import workbench from '@hcengineering/workbench' import workbench from '@hcengineering/workbench'
import { Channel } from '@hcengineering/chunter' import { Channel } from '@hcengineering/chunter'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { openChannel } from '../../../navigation'
import { get } from 'svelte/store'
import notification from '@hcengineering/notification'
import { getObjectIcon, joinChannel, leaveChannel } from '../../../utils' import { getObjectIcon, joinChannel, leaveChannel } from '../../../utils'
import chunter from './../../../plugin' import chunter from './../../../plugin'
@ -54,12 +42,9 @@
export let withFilterButton: boolean = true export let withFilterButton: boolean = true
export let search: string = '' export let search: string = ''
const client = getClient()
const me = getCurrentAccount()._id const me = getCurrentAccount()._id
const channelsQuery = createQuery() const channelsQuery = createQuery()
const notificationClient = InboxNotificationsClientImpl.getClient()
const sort: SortingQuery<Space> = { const sort: SortingQuery<Space> = {
name: SortingOrder.Ascending name: SortingOrder.Ascending
} }
@ -114,28 +99,7 @@
} }
async function view (channel: Channel): Promise<void> { async function view (channel: Channel): Promise<void> {
const loc = getCurrentResolvedLocation() openChannel(channel._id, channel._class)
const context = get(notificationClient.contextByDoc).get(channel._id)
let contextId = context?._id
if (contextId === undefined) {
contextId = await client.createDoc(notification.class.DocNotifyContext, channel.space, {
attachedToClass: channel._class,
attachedTo: channel._id,
user: me,
hidden: false,
lastViewedTimestamp: Date.now()
})
}
if (contextId === undefined) {
return
}
loc.path[3] = contextId
navigate(loc)
} }
</script> </script>

View File

@ -21,7 +21,7 @@
import { ActivityMessagePresenter } from '@hcengineering/activity-resources' import { ActivityMessagePresenter } from '@hcengineering/activity-resources'
import plugin from '../../../plugin' import plugin from '../../../plugin'
import { openMessageFromSpecial } from '../../../utils' import { openMessageFromSpecial } from '../../../navigation'
export let withHeader: boolean = true export let withHeader: boolean = true
export let search: string = '' export let search: string = ''

View File

@ -24,9 +24,9 @@
import { ActivityMessagePresenter, savedMessagesStore } from '@hcengineering/activity-resources' import { ActivityMessagePresenter, savedMessagesStore } from '@hcengineering/activity-resources'
import chunter from '../../../plugin' import chunter from '../../../plugin'
import { openMessageFromSpecial } from '../../../utils'
import { savedAttachmentsStore } from '../utils' import { savedAttachmentsStore } from '../utils'
import Header from '../../Header.svelte' import Header from '../../Header.svelte'
import { openMessageFromSpecial } from '../../../navigation'
const client = getClient() const client = getClient()

View File

@ -17,8 +17,10 @@ import { type Doc, type DocumentQuery, type Ref } from '@hcengineering/core'
import { type DocNotifyContext } from '@hcengineering/notification' import { type DocNotifyContext } from '@hcengineering/notification'
import { type AnySvelteComponent, type IconSize, type Action } from '@hcengineering/ui' import { type AnySvelteComponent, type IconSize, type Action } from '@hcengineering/ui'
export type ChatGroup = 'activity' | 'direct' | 'channels' | 'starred'
export interface ChatNavGroupModel { export interface ChatNavGroupModel {
id: string id: ChatGroup
label?: IntlString label?: IntlString
query: DocumentQuery<DocNotifyContext> query: DocumentQuery<DocNotifyContext>
sortFn: (items: ChatNavItemModel[], contexts: DocNotifyContext[]) => ChatNavItemModel[] sortFn: (items: ChatNavItemModel[], contexts: DocNotifyContext[]) => ChatNavItemModel[]
@ -37,3 +39,7 @@ export interface ChatNavItemModel {
isSecondary: boolean isSecondary: boolean
withIconBackground: boolean withIconBackground: boolean
} }
export interface SelectChannelEvent {
object: Doc
}

View File

@ -22,7 +22,7 @@
import chunter from '../../plugin' import chunter from '../../plugin'
import Header from '../Header.svelte' import Header from '../Header.svelte'
import { openMessageFromSpecial } from '../../utils' import { openMessageFromSpecial } from '../../navigation'
const threadsQuery = createQuery() const threadsQuery = createQuery()
const me = getCurrentAccount() as PersonAccount const me = getCurrentAccount() as PersonAccount

View File

@ -13,17 +13,14 @@
// limitations under the License. // limitations under the License.
// //
import { get, writable } from 'svelte/store' import { writable } from 'svelte/store'
import chunter, { type Channel, type ChatMessage, chunterId, type DirectMessage } from '@hcengineering/chunter' import chunter, { type Channel, type ChatMessage, type DirectMessage } from '@hcengineering/chunter'
import { getCurrentAccount, type Ref } from '@hcengineering/core'
import { type Resources } from '@hcengineering/platform' import { type Resources } from '@hcengineering/platform'
import { MessageBox, getClient } from '@hcengineering/presentation' import { MessageBox, getClient } from '@hcengineering/presentation'
import { closePanel, getCurrentLocation, getLocation, navigate, showPopup } from '@hcengineering/ui' import { getLocation, navigate, showPopup } from '@hcengineering/ui'
import { type ActivityMessage } from '@hcengineering/activity' import { type ActivityMessage } from '@hcengineering/activity'
import notification, { type DocNotifyContext, notificationId } from '@hcengineering/notification'
import ChannelPresenter from './components/ChannelPresenter.svelte' import ChannelPresenter from './components/ChannelPresenter.svelte'
import ChannelView from './components/ChannelView.svelte'
import ChannelPanel from './components/ChannelPanel.svelte' import ChannelPanel from './components/ChannelPanel.svelte'
import ChunterBrowser from './components/chat/specials/ChunterBrowser.svelte' import ChunterBrowser from './components/chat/specials/ChunterBrowser.svelte'
import ConvertDmToPrivateChannelModal from './components/ConvertDmToPrivateChannel.svelte' import ConvertDmToPrivateChannelModal from './components/ConvertDmToPrivateChannel.svelte'
@ -60,19 +57,15 @@ import {
ChannelTitleProvider, ChannelTitleProvider,
DirectTitleProvider, DirectTitleProvider,
canDeleteMessage, canDeleteMessage,
chunterSpaceLinkFragmentProvider,
dmIdentifierProvider, dmIdentifierProvider,
getDmName, getDmName,
getMessageLink,
getTitle, getTitle,
getUnreadThreadsCount, getUnreadThreadsCount,
canCopyMessageLink, canCopyMessageLink,
buildThreadLink,
getThreadLink,
leaveChannelAction, leaveChannelAction,
removeChannelAction removeChannelAction
} from './utils' } from './utils'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { chunterSpaceLinkFragmentProvider, getThreadLink, getMessageLink } from './navigation'
export { default as ChatMessagesPresenter } from './components/chat-message/ChatMessagesPresenter.svelte' export { default as ChatMessagesPresenter } from './components/chat-message/ChatMessagesPresenter.svelte'
export { default as ChatMessagePopup } from './components/chat-message/ChatMessagePopup.svelte' export { default as ChatMessagePopup } from './components/chat-message/ChatMessagePopup.svelte'
@ -132,34 +125,6 @@ async function ConvertDmToPrivateChannel (dm: DirectMessage): Promise<void> {
}) })
} }
export async function openChannel (
notifyContext?: DocNotifyContext,
evt?: Event,
props?: { _id: Ref<DocNotifyContext> }
): Promise<void> {
evt?.preventDefault()
closePanel()
const loc = getCurrentLocation()
const id = notifyContext?._id ?? props?._id
if (id === undefined) {
return
}
if (loc.path[3] === id) {
return
}
loc.path[3] = id
loc.path.length = 4
loc.query = { message: null }
loc.fragment = undefined
navigate(loc)
}
export const userSearch = writable('') export const userSearch = writable('')
export async function chunterBrowserVisible (): Promise<boolean> { export async function chunterBrowserVisible (): Promise<boolean> {
@ -176,34 +141,7 @@ export async function deleteChatMessage (message: ChatMessage): Promise<void> {
await client.remove(message) await client.remove(message)
} }
export async function replyToThread (message: ActivityMessage): Promise<void> { export { replyToThread } from './navigation'
const loc = getCurrentLocation()
const client = getClient()
const inboxClient = InboxNotificationsClientImpl.getClient()
let contextId: Ref<DocNotifyContext> | undefined = get(inboxClient.contextByDoc).get(message.attachedTo)?._id
if (contextId === undefined) {
contextId = await client.createDoc(notification.class.DocNotifyContext, message.space, {
attachedTo: message.attachedTo,
attachedToClass: message.attachedToClass,
user: getCurrentAccount()._id,
hidden: false,
lastViewedTimestamp: Date.now()
})
}
if (contextId === undefined) {
return
}
if (loc.path[2] !== notificationId) {
loc.path[2] = chunterId
}
navigate(buildThreadLink(loc, contextId, message._id))
}
export default async (): Promise<Resources> => ({ export default async (): Promise<Resources> => ({
filter: { filter: {
@ -215,7 +153,6 @@ export default async (): Promise<Resources> => ({
ThreadParentPresenter, ThreadParentPresenter,
ThreadViewPanel, ThreadViewPanel,
ChannelHeader, ChannelHeader,
ChannelView,
ChannelPanel, ChannelPanel,
ChannelPresenter, ChannelPresenter,
DirectMessagePresenter, DirectMessagePresenter,
@ -262,7 +199,6 @@ export default async (): Promise<Resources> => ({
UnarchiveChannel, UnarchiveChannel,
ConvertDmToPrivateChannel, ConvertDmToPrivateChannel,
DeleteChatMessage: deleteChatMessage, DeleteChatMessage: deleteChatMessage,
OpenChannel: openChannel,
LeaveChannel: leaveChannelAction, LeaveChannel: leaveChannelAction,
RemoveChannel: removeChannelAction RemoveChannel: removeChannelAction
} }

View File

@ -0,0 +1,139 @@
import { getCurrentLocation, getCurrentResolvedLocation, getLocation, type Location, navigate } from '@hcengineering/ui'
import { type Ref, type Doc, type Class } from '@hcengineering/core'
import type { ActivityMessage } from '@hcengineering/activity'
import { chunterId, type ChunterSpace, type ThreadMessage } from '@hcengineering/chunter'
import { notificationId } from '@hcengineering/notification'
import { workbenchId } from '@hcengineering/workbench'
import { chatSpecials } from './components/chat/utils'
import { isThreadMessage } from './utils'
export function decodeChannelURI (value: string): [Ref<Doc>, Ref<Class<Doc>>] {
return decodeURIComponent(value).split('|') as [Ref<Doc>, Ref<Class<Doc>>]
}
function encodeChannelURI (_id: Ref<Doc>, _class: Ref<Class<Doc>>): string {
return encodeURIComponent([_id, _class].join('|'))
}
export function openChannel (_id: Ref<Doc>, _class: Ref<Class<Doc>>): void {
const loc = getCurrentLocation()
const id = encodeChannelURI(_id, _class)
if (loc.path[3] === id) {
return
}
loc.path[3] = id
loc.path[4] = ''
loc.query = { ...loc.query, message: null }
loc.path.length = 4
navigate(loc)
}
export async function openMessageFromSpecial (message?: ActivityMessage): Promise<void> {
if (message === undefined) {
return
}
const loc = getCurrentResolvedLocation()
if (isThreadMessage(message)) {
loc.path[3] = encodeChannelURI(message.objectId, message.objectClass)
loc.path[4] = message.attachedTo
} else {
loc.path[3] = encodeChannelURI(message.attachedTo, message.attachedToClass)
}
loc.query = { ...loc.query, message: message._id }
navigate(loc)
}
export function navigateToSpecial (specialId: string): void {
const loc = getLocation()
loc.path[2] = chunterId
loc.path[3] = specialId
navigate(loc)
}
export async function getMessageLink (message: ActivityMessage): Promise<string> {
const location = getCurrentResolvedLocation()
let threadParent = ''
let _id: Ref<Doc>
let _class: Ref<Class<Doc>>
if (isThreadMessage(message)) {
threadParent = `/${message.attachedTo}`
_id = message.objectId
_class = message.objectClass
} else {
_id = message.attachedTo
_class = message.attachedToClass
}
const id = encodeChannelURI(_id, _class)
return `${window.location.protocol}//${window.location.host}/${workbenchId}/${location.path[1]}/${chunterId}/${id}${threadParent}?message=${message._id}`
}
export async function chunterSpaceLinkFragmentProvider (doc: ChunterSpace): Promise<Location> {
const loc = getCurrentResolvedLocation()
loc.path.length = 2
loc.fragment = undefined
loc.query = undefined
loc.path[2] = chunterId
loc.path[3] = encodeChannelURI(doc._id, doc._class)
return loc
}
export function buildThreadLink (
loc: Location,
_id: Ref<Doc>,
_class: Ref<Class<Doc>>,
threadParent: Ref<ActivityMessage>
): Location {
const specials = chatSpecials.map(({ id }) => id)
const id = encodeChannelURI(_id, _class)
const isSameChannel = loc.path[3] === id
if (!isSameChannel) {
loc.query = { message: threadParent }
}
if (loc.path[2] === chunterId && specials.includes(loc.path[3])) {
loc.path[4] = threadParent
return loc
}
if (loc.path[2] !== notificationId) {
loc.path[2] = chunterId
}
loc.path[3] = id
loc.path[4] = threadParent
loc.fragment = undefined
return loc
}
export async function getThreadLink (doc: ThreadMessage): Promise<Location> {
const loc = getCurrentResolvedLocation()
return buildThreadLink(loc, doc.objectId, doc.objectClass, doc.attachedTo)
}
export async function replyToThread (message: ActivityMessage): Promise<void> {
const loc = getCurrentLocation()
if (loc.path[2] !== notificationId) {
loc.path[2] = chunterId
}
navigate(buildThreadLink(loc, message.attachedTo, message.attachedToClass, message._id))
}

View File

@ -54,7 +54,6 @@ export default mergeIds(chunterId, chunter, {
UnsubscribeMessage: '' as ViewAction, UnsubscribeMessage: '' as ViewAction,
SubscribeComment: '' as ViewAction, SubscribeComment: '' as ViewAction,
UnsubscribeComment: '' as ViewAction, UnsubscribeComment: '' as ViewAction,
OpenChannel: '' as ViewAction,
LeaveChannel: '' as ViewAction, LeaveChannel: '' as ViewAction,
RemoveChannel: '' as ViewAction RemoveChannel: '' as ViewAction
}, },

View File

@ -12,14 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
import { import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage } from '@hcengineering/chunter'
type Channel,
type ChatMessage,
chunterId,
type ChunterSpace,
type DirectMessage,
type ThreadMessage
} from '@hcengineering/chunter'
import contact, { type Employee, type PersonAccount, getName, type Person } from '@hcengineering/contact' import contact, { type Employee, type PersonAccount, getName, type Person } from '@hcengineering/contact'
import { employeeByIdStore, PersonIcon } from '@hcengineering/contact-resources' import { employeeByIdStore, PersonIcon } from '@hcengineering/contact-resources'
import { import {
@ -35,14 +28,7 @@ import {
generateId generateId
} from '@hcengineering/core' } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import { import { type AnySvelteComponent } from '@hcengineering/ui'
type AnySvelteComponent,
getCurrentResolvedLocation,
getLocation,
type Location,
navigate
} from '@hcengineering/ui'
import { workbenchId } from '@hcengineering/workbench'
import { type Asset, getResource, translate } from '@hcengineering/platform' import { type Asset, getResource, translate } from '@hcengineering/platform'
import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources' import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
import activity, { import activity, {
@ -57,13 +43,12 @@ import {
InboxNotificationsClientImpl, InboxNotificationsClientImpl,
isMentionNotification isMentionNotification
} from '@hcengineering/notification-resources' } from '@hcengineering/notification-resources'
import notification, { type DocNotifyContext, notificationId } from '@hcengineering/notification' import notification, { type DocNotifyContext } from '@hcengineering/notification'
import { get, type Unsubscriber } from 'svelte/store' import { get, type Unsubscriber } from 'svelte/store'
import chunter from './plugin' import chunter from './plugin'
import DirectIcon from './components/DirectIcon.svelte' import DirectIcon from './components/DirectIcon.svelte'
import ChannelIcon from './components/ChannelIcon.svelte' import ChannelIcon from './components/ChannelIcon.svelte'
import { chatSpecials } from './components/chat/utils'
export async function getDmName (client: Client, space?: Space): Promise<string> { export async function getDmName (client: Client, space?: Space): Promise<string> {
if (space === undefined) { if (space === undefined) {
@ -205,40 +190,6 @@ export async function ChannelTitleProvider (client: Client, id: Ref<Channel>): P
return channel.name return channel.name
} }
export async function openMessageFromSpecial (message?: ActivityMessage): Promise<void> {
if (message === undefined) {
return
}
const inboxClient = InboxNotificationsClientImpl.getClient()
const loc = getCurrentResolvedLocation()
if (message._class === chunter.class.ThreadMessage) {
const threadMessage = message as ThreadMessage
loc.path[4] = threadMessage.attachedTo
} else {
const context = get(inboxClient.contextByDoc).get(message.attachedTo)
if (context === undefined) {
return
}
loc.path[4] = context._id
}
loc.query = { ...loc.query, message: message._id }
navigate(loc)
}
export function navigateToSpecial (specialId: string): void {
const loc = getLocation()
loc.path[2] = chunterId
loc.path[3] = specialId
navigate(loc)
}
export enum SearchType { export enum SearchType {
Messages, Messages,
Channels, Channels,
@ -246,28 +197,6 @@ export enum SearchType {
Contacts Contacts
} }
export async function getMessageLink (message: ActivityMessage): Promise<string> {
const inboxClient = InboxNotificationsClientImpl.getClient()
const location = getCurrentResolvedLocation()
let context: DocNotifyContext | undefined
let threadParent: string = ''
if (message._class === chunter.class.ThreadMessage) {
const threadMessage = message as ThreadMessage
threadParent = `/${threadMessage.attachedTo}`
context = get(inboxClient.contextByDoc).get(threadMessage.objectId)
} else {
context = get(inboxClient.contextByDoc).get(message.attachedTo)
}
if (context === undefined) {
return ''
}
return `${window.location.protocol}//${window.location.host}/${workbenchId}/${location.path[1]}/${chunterId}/${context._id}${threadParent}?message=${message._id}`
}
export async function getTitle (doc: Doc): Promise<string> { export async function getTitle (doc: Doc): Promise<string> {
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
@ -281,24 +210,6 @@ export async function getTitle (doc: Doc): Promise<string> {
return `${label}-${doc._id}` return `${label}-${doc._id}`
} }
export async function chunterSpaceLinkFragmentProvider (doc: ChunterSpace): Promise<Location> {
const inboxClient = InboxNotificationsClientImpl.getClient()
const context = get(inboxClient.contextByDoc).get(doc._id)
const loc = getCurrentResolvedLocation()
if (context === undefined) {
return loc
}
loc.path.length = 2
loc.fragment = undefined
loc.query = undefined
loc.path[2] = chunterId
loc.path[3] = context._id
return loc
}
export function getObjectIcon (_class: Ref<Class<Doc>>): Asset | AnySvelteComponent | undefined { export function getObjectIcon (_class: Ref<Class<Doc>>): Asset | AnySvelteComponent | undefined {
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
@ -387,54 +298,6 @@ export async function filterChatMessages (
return messages.filter((message) => filtersFns.some((filterFn) => filterFn(message, objectClass))) return messages.filter((message) => filtersFns.some((filterFn) => filterFn(message, objectClass)))
} }
export function buildThreadLink (loc: Location, contextId: Ref<DocNotifyContext>, _id: Ref<ActivityMessage>): Location {
const specials = chatSpecials.map(({ id }) => id)
const isSameContext = loc.path[3] === contextId
if (!isSameContext) {
loc.query = { message: _id }
}
if (loc.path[2] === chunterId && specials.includes(loc.path[3])) {
loc.path[4] = _id
return loc
}
if (loc.path[2] !== notificationId) {
loc.path[2] = chunterId
}
loc.path[3] = contextId
loc.path[4] = _id
loc.fragment = undefined
return loc
}
export async function getThreadLink (doc: ThreadMessage): Promise<Location> {
const loc = getCurrentResolvedLocation()
const client = getClient()
const inboxClient = InboxNotificationsClientImpl.getClient()
let contextId: Ref<DocNotifyContext> | undefined = get(inboxClient.contextByDoc).get(doc.objectId)?._id
if (contextId === undefined) {
contextId = await client.createDoc(notification.class.DocNotifyContext, doc.space, {
attachedTo: doc.attachedTo,
attachedToClass: doc.attachedToClass,
user: getCurrentAccount()._id,
hidden: false,
lastViewedTimestamp: Date.now()
})
}
if (contextId === undefined) {
return loc
}
return buildThreadLink(loc, contextId, doc.attachedTo)
}
export async function joinChannel (channel: Channel, value: Ref<Account> | Array<Ref<Account>>): Promise<void> { export async function joinChannel (channel: Channel, value: Ref<Account> | Array<Ref<Account>>): Promise<void> {
const client = getClient() const client = getClient()
@ -533,3 +396,7 @@ export async function removeChannelAction (context?: DocNotifyContext): Promise<
await deleteContextNotifications(context) await deleteContextNotifications(context)
await client.remove(context) await client.remove(context)
} }
export function isThreadMessage (message: ActivityMessage): message is ThreadMessage {
return message._class === chunter.class.ThreadMessage
}

View File

@ -130,7 +130,6 @@ export default plugin(chunterId, {
}, },
component: { component: {
DmHeader: '' as AnyComponent, DmHeader: '' as AnyComponent,
ChannelView: '' as AnyComponent,
ThreadView: '' as AnyComponent, ThreadView: '' as AnyComponent,
Thread: '' as AnyComponent, Thread: '' as AnyComponent,
Reactions: '' as AnyComponent, Reactions: '' as AnyComponent,
@ -211,7 +210,6 @@ export default plugin(chunterId, {
}, },
action: { action: {
DeleteChatMessage: '' as Ref<Action>, DeleteChatMessage: '' as Ref<Action>,
OpenChannel: '' as Ref<Action>,
LeaveChannel: '' as Ref<Action>, LeaveChannel: '' as Ref<Action>,
RemoveChannel: '' as Ref<Action>, RemoveChannel: '' as Ref<Action>,
CloseConversation: '' as Ref<Action> CloseConversation: '' as Ref<Action>

View File

@ -45,6 +45,7 @@
import Filter from '../Filter.svelte' import Filter from '../Filter.svelte'
import { import {
archiveAll, archiveAll,
decodeObjectURI,
getDisplayInboxData, getDisplayInboxData,
isMentionNotification, isMentionNotification,
openInboxDoc, openInboxDoc,
@ -65,6 +66,7 @@
const inboxClient = InboxNotificationsClientImpl.getClient() const inboxClient = InboxNotificationsClientImpl.getClient()
const notificationsByContextStore = inboxClient.inboxNotificationsByContext const notificationsByContextStore = inboxClient.inboxNotificationsByContext
const contextByIdStore = inboxClient.contextById const contextByIdStore = inboxClient.contextById
const contextByDocStore = inboxClient.contextByDoc
const contextsStore = inboxClient.contexts const contextsStore = inboxClient.contexts
const allTab: TabItem = { const allTab: TabItem = {
@ -98,8 +100,10 @@
async function syncLocation (newLocation: Location): Promise<void> { async function syncLocation (newLocation: Location): Promise<void> {
const loc = await resolveLocation(newLocation) const loc = await resolveLocation(newLocation)
const [_id] = decodeObjectURI(loc?.loc.path[3] ?? '')
const context = $contextByDocStore.get(_id)
selectedContextId = loc?.loc.path[3] as Ref<DocNotifyContext> | undefined selectedContextId = context?._id
if (selectedContextId !== selectedContext?._id) { if (selectedContextId !== selectedContext?._id) {
selectedContext = undefined selectedContext = undefined
@ -179,7 +183,8 @@
const selectedMsg = selectedNotification.mentionedIn as Ref<ActivityMessage> const selectedMsg = selectedNotification.mentionedIn as Ref<ActivityMessage>
openInboxDoc( openInboxDoc(
selectedContext._id, selectedContext.attachedTo,
selectedContext.attachedToClass,
isActivityMessageClass(selectedContext.attachedToClass) isActivityMessageClass(selectedContext.attachedToClass)
? (selectedContext.attachedTo as Ref<ActivityMessage>) ? (selectedContext.attachedTo as Ref<ActivityMessage>)
: undefined, : undefined,
@ -192,20 +197,31 @@
const thread = await client.findOne(chunter.class.ThreadMessage, { const thread = await client.findOne(chunter.class.ThreadMessage, {
_id: selectedContext.attachedTo as Ref<ThreadMessage> _id: selectedContext.attachedTo as Ref<ThreadMessage>
}) })
openInboxDoc(selectedContext._id, thread?.attachedTo, thread?._id) openInboxDoc(selectedContext.attachedTo, selectedContext.attachedToClass, thread?.attachedTo, thread?._id)
} else if (isReactionMessage(message)) { } else if (isReactionMessage(message)) {
openInboxDoc(selectedContext._id, undefined, selectedContext.attachedTo as Ref<ActivityMessage>) openInboxDoc(
selectedContext.attachedTo,
selectedContext.attachedToClass,
undefined,
selectedContext.attachedTo as Ref<ActivityMessage>
)
} else { } else {
const selectedMsg = (selectedNotification as ActivityInboxNotification)?.attachedTo const selectedMsg = (selectedNotification as ActivityInboxNotification)?.attachedTo
openInboxDoc( openInboxDoc(
selectedContext._id, selectedContext.attachedTo,
selectedContext.attachedToClass,
selectedMsg ? (selectedContext.attachedTo as Ref<ActivityMessage>) : undefined, selectedMsg ? (selectedContext.attachedTo as Ref<ActivityMessage>) : undefined,
selectedMsg ?? (selectedContext.attachedTo as Ref<ActivityMessage>) selectedMsg ?? (selectedContext.attachedTo as Ref<ActivityMessage>)
) )
} }
} else { } else {
openInboxDoc(selectedContext._id, undefined, (selectedNotification as ActivityInboxNotification)?.attachedTo) openInboxDoc(
selectedContext.attachedTo,
selectedContext.attachedToClass,
undefined,
(selectedNotification as ActivityInboxNotification)?.attachedTo
)
} }
} }

View File

@ -409,9 +409,9 @@ export async function resolveLocation (loc: Location): Promise<ResolvedLocation
return undefined return undefined
} }
const contextId = loc.path[3] as Ref<DocNotifyContext> | undefined const [_id, _class] = decodeObjectURI(loc.path[3])
if (contextId === undefined) { if (_id === undefined || _class === undefined) {
return { return {
loc: { loc: {
path: [loc.path[0], loc.path[1], notificationId], path: [loc.path[0], loc.path[1], notificationId],
@ -424,12 +424,13 @@ export async function resolveLocation (loc: Location): Promise<ResolvedLocation
} }
} }
return await generateLocation(loc, contextId) return await generateLocation(loc, _id, _class)
} }
async function generateLocation ( async function generateLocation (
loc: Location, loc: Location,
contextId: Ref<DocNotifyContext> _id: Ref<Doc>,
_class: Ref<Class<Doc>>
): Promise<ResolvedLocation | undefined> { ): Promise<ResolvedLocation | undefined> {
const client = getClient() const client = getClient()
@ -437,35 +438,18 @@ async function generateLocation (
const workspace = loc.path[1] ?? '' const workspace = loc.path[1] ?? ''
const threadId = loc.path[4] as Ref<ActivityMessage> | undefined const threadId = loc.path[4] as Ref<ActivityMessage> | undefined
const contextNotification = await client.findOne(notification.class.InboxNotification, {
docNotifyContext: contextId
})
if (contextNotification === undefined) {
return {
loc: {
path: [loc.path[0], loc.path[1], notificationId],
fragment: undefined
},
defaultLocation: {
path: [loc.path[0], loc.path[1], notificationId],
fragment: undefined
}
}
}
const thread = const thread =
threadId !== undefined ? await client.findOne(activity.class.ActivityMessage, { _id: threadId }) : undefined threadId !== undefined ? await client.findOne(activity.class.ActivityMessage, { _id: threadId }) : undefined
if (thread === undefined) { if (thread === undefined) {
return { return {
loc: { loc: {
path: [appComponent, workspace, notificationId, contextId], path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class)],
fragment: undefined, fragment: undefined,
query: { ...loc.query } query: { ...loc.query }
}, },
defaultLocation: { defaultLocation: {
path: [appComponent, workspace, notificationId, contextId], path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class)],
fragment: undefined, fragment: undefined,
query: { ...loc.query } query: { ...loc.query }
} }
@ -474,20 +458,29 @@ async function generateLocation (
return { return {
loc: { loc: {
path: [appComponent, workspace, notificationId, contextId, threadId as string], path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class), threadId as string],
fragment: undefined, fragment: undefined,
query: { ...loc.query } query: { ...loc.query }
}, },
defaultLocation: { defaultLocation: {
path: [appComponent, workspace, notificationId, contextId, threadId as string], path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class), threadId as string],
fragment: undefined, fragment: undefined,
query: { ...loc.query } query: { ...loc.query }
} }
} }
} }
export function decodeObjectURI (value: string): [Ref<Doc>, Ref<Class<Doc>>] {
return decodeURIComponent(value).split('|') as [Ref<Doc>, Ref<Class<Doc>>]
}
function encodeObjectURI (_id: Ref<Doc>, _class: Ref<Class<Doc>>): string {
return encodeURIComponent([_id, _class].join('|'))
}
export function openInboxDoc ( export function openInboxDoc (
contextId?: Ref<DocNotifyContext>, _id?: Ref<Doc>,
_class?: Ref<Class<Doc>>,
thread?: Ref<ActivityMessage>, thread?: Ref<ActivityMessage>,
message?: Ref<ActivityMessage> message?: Ref<ActivityMessage>
): void { ): void {
@ -497,14 +490,14 @@ export function openInboxDoc (
return return
} }
if (contextId === undefined) { if (_id === undefined || _class === undefined) {
loc.query = { ...loc.query, message: null } loc.query = { message: null }
loc.path.length = 3 loc.path.length = 3
navigate(loc) navigate(loc)
return return
} }
loc.path[3] = contextId loc.path[3] = encodeObjectURI(_id, _class)
if (thread !== undefined) { if (thread !== undefined) {
loc.path[4] = thread loc.path[4] = thread