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

View File

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

View File

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

View File

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

View File

@ -21,9 +21,11 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import { SearchEdit } from '@hcengineering/ui'
import { openDoc } from '@hcengineering/view-resources'
import { userSearch } from '../index'
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 withSearch: boolean = true

View File

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

View File

@ -25,9 +25,8 @@
} from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform'
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 embedded = false
@ -71,14 +70,14 @@
.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)
.map((id) => personById.get(id))
.filter((person): person is Person => person !== undefined)
.slice(0, maxDisplayPersons - 1)
}
function handleReply (e: any) {
function handleReply (e: MouseEvent): void {
e.stopPropagation()
e.preventDefault()
@ -87,17 +86,7 @@
return
}
if (inboxClient === undefined) {
return
}
const context = get(inboxClient.contextByDoc).get(object.attachedTo)
if (context === undefined) {
return
}
navigate(buildThreadLink(getLocation(), context._id, object._id))
navigate(buildThreadLink(getLocation(), object.attachedTo, object.attachedToClass, object._id))
}
</script>

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@
import chunter from '../../../plugin'
import { buildDmName } from '../../../utils'
import ChannelMembers from '../../ChannelMembers.svelte'
import { openChannel } from '../../../index'
import { openChannel } from '../../../navigation'
const dispatch = createEventDispatcher()
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 =
direct?._id ??
(await client.createDoc(chunter.class.DirectMessage, core.space.Space, {
@ -89,14 +74,27 @@
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,
attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage,
hidden: false
})
await openChannel(undefined, undefined, { _id: notifyContextId })
openChannel(dmId, chunter.class.DirectMessage)
}
function handleCancel (): void {

View File

@ -20,11 +20,12 @@
import { translate } from '@hcengineering/platform'
import { Action } from '@hcengineering/ui'
import { ChatNavGroupModel } from '../types'
import { ChatGroup, ChatNavGroupModel } from '../types'
import ChatNavSection from './ChatNavSection.svelte'
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
interface Section {
@ -43,6 +44,8 @@
let objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>()
let contexts: DocNotifyContext[] = []
let shouldPushObject = false
let sections: Section[] = []
$: contextsQuery.query(
@ -63,10 +66,15 @@
$: loadObjects(contexts)
$: void getSections(objectsByClass, model).then((res) => {
$: void getSections(objectsByClass, model, shouldPushObject ? object : undefined).then((res) => {
sections = res
})
$: shouldPushObject =
object !== undefined &&
getObjectGroup(object) === model.id &&
!contexts.some(({ attachedTo }) => attachedTo === object?._id)
function loadObjects (contexts: DocNotifyContext[]): void {
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 (
objectsByClass: Map<Ref<Class<Doc>>, Doc[]>,
model: ChatNavGroupModel
model: ChatNavGroupModel,
object: Doc | undefined
): Promise<Section[]> {
const result: Section[] = []
@ -108,13 +129,40 @@
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()) {
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({
id: _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, {})
})
}
@ -141,7 +189,7 @@
<ChatNavSection
objects={section.objects}
{contexts}
{selectedContextId}
{objectId}
header={section.label}
actions={getSectionActions(section, contexts)}
sortFn={model.sortFn}

View File

@ -25,11 +25,14 @@
isMentionNotification
} from '@hcengineering/notification-resources'
import { createEventDispatcher } from 'svelte'
import view from '@hcengineering/view'
import { Doc } from '@hcengineering/core'
import NavItem from './NavItem.svelte'
import { ChatNavItemModel } from '../types'
import { openChannel } from '../../../navigation'
export let context: DocNotifyContext
export let context: DocNotifyContext | undefined
export let item: ChatNavItemModel
export let isSelected = false
@ -43,6 +46,9 @@
let actions: Action[] = []
notificationClient.inboxNotificationsByContext.subscribe((res) => {
if (context === undefined) {
return
}
notifications = (res.get(context._id) ?? []).filter((n) => isMentionNotification(n) || isActivityNotification(n))
})
@ -50,12 +56,25 @@
notificationsCount = res
})
$: void getChannelActions(context).then((res) => {
$: void getChannelActions(context, item.object).then((res) => {
actions = res
})
async function getChannelActions (context: DocNotifyContext): Promise<Action[]> {
const result = []
async function getChannelActions (context: DocNotifyContext | undefined, object: Doc): Promise<Action[]> {
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 = [
notification.action.DeleteContextNotifications,
notification.action.UnReadNotifyContext,
@ -100,6 +119,6 @@
description={item.description}
{actions}
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 actions: Action[] = []
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[]
const client = getClient()
@ -52,7 +52,7 @@
$: 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[]> {
const items: ChatNavItemModel[] = []
@ -98,8 +98,7 @@
isShownMore: boolean,
maxItems: number | undefined,
items: ChatNavItemModel[],
selectedContextId: Ref<DocNotifyContext> | undefined,
contexts: DocNotifyContext[]
selectedObjectId: Ref<Doc> | undefined
): ChatNavItemModel[] {
if (!canShowMore || isShownMore) {
return items
@ -107,23 +106,17 @@
const result = items.slice(0, maxItems)
if (selectedContextId === undefined) {
if (selectedObjectId === undefined) {
return result
}
const context = contexts.find(({ _id }) => _id === selectedContextId)
if (context === undefined) {
return result
}
const exists = result.some(({ id }) => id === context.attachedTo)
const exists = result.some(({ id }) => id === selectedObjectId)
if (exists) {
return result
}
const selectedItem = items.find(({ id }) => id === context?.attachedTo)
const selectedItem = items.find(({ id }) => id === selectedObjectId)
if (selectedItem === undefined) {
return result
@ -148,9 +141,7 @@
{#if !isCollapsed}
{#each visibleItems as item (item.id)}
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
{#if context}
<ChatNavItem {context} isSelected={selectedContextId === context._id} {item} on:select />
{/if}
<ChatNavItem {context} isSelected={objectId === item.id} {item} on:select />
{/each}
{#if canShowMore}
<div class="showMore">

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<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 { DocNotifyContext } from '@hcengineering/notification'
import { SpecialNavModel } from '@hcengineering/workbench'
@ -27,9 +27,10 @@
import { chatNavGroupModels, chatSpecials } from '../utils'
import ChatSpecialElement from './ChatSpecialElement.svelte'
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
const notificationClient = InboxNotificationsClientImpl.getClient()
@ -105,7 +106,7 @@
</div>
<Scroller>
{#each chatNavGroupModels as model}
<ChatNavGroup {selectedContextId} {model} on:select />
<ChatNavGroup {object} {objectId} {model} on:select />
{/each}
<div class="antiNav-space" />
</Scroller>

View File

@ -24,24 +24,12 @@
Space
} from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import {
AnyComponent,
Button,
getCurrentResolvedLocation,
Icon,
Label,
navigate,
Scroller,
SearchEdit,
showPopup
} from '@hcengineering/ui'
import presentation, { createQuery } from '@hcengineering/presentation'
import { AnyComponent, Button, Icon, Label, Scroller, SearchEdit, showPopup } from '@hcengineering/ui'
import { FilterBar, FilterButton, SpacePresenter } from '@hcengineering/view-resources'
import workbench from '@hcengineering/workbench'
import { Channel } from '@hcengineering/chunter'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { get } from 'svelte/store'
import notification from '@hcengineering/notification'
import { openChannel } from '../../../navigation'
import { getObjectIcon, joinChannel, leaveChannel } from '../../../utils'
import chunter from './../../../plugin'
@ -54,12 +42,9 @@
export let withFilterButton: boolean = true
export let search: string = ''
const client = getClient()
const me = getCurrentAccount()._id
const channelsQuery = createQuery()
const notificationClient = InboxNotificationsClientImpl.getClient()
const sort: SortingQuery<Space> = {
name: SortingOrder.Ascending
}
@ -114,28 +99,7 @@
}
async function view (channel: Channel): Promise<void> {
const loc = getCurrentResolvedLocation()
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)
openChannel(channel._id, channel._class)
}
</script>

View File

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

View File

@ -24,9 +24,9 @@
import { ActivityMessagePresenter, savedMessagesStore } from '@hcengineering/activity-resources'
import chunter from '../../../plugin'
import { openMessageFromSpecial } from '../../../utils'
import { savedAttachmentsStore } from '../utils'
import Header from '../../Header.svelte'
import { openMessageFromSpecial } from '../../../navigation'
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 AnySvelteComponent, type IconSize, type Action } from '@hcengineering/ui'
export type ChatGroup = 'activity' | 'direct' | 'channels' | 'starred'
export interface ChatNavGroupModel {
id: string
id: ChatGroup
label?: IntlString
query: DocumentQuery<DocNotifyContext>
sortFn: (items: ChatNavItemModel[], contexts: DocNotifyContext[]) => ChatNavItemModel[]
@ -37,3 +39,7 @@ export interface ChatNavItemModel {
isSecondary: boolean
withIconBackground: boolean
}
export interface SelectChannelEvent {
object: Doc
}

View File

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

View File

@ -13,17 +13,14 @@
// limitations under the License.
//
import { get, writable } from 'svelte/store'
import chunter, { type Channel, type ChatMessage, chunterId, type DirectMessage } from '@hcengineering/chunter'
import { getCurrentAccount, type Ref } from '@hcengineering/core'
import { writable } from 'svelte/store'
import chunter, { type Channel, type ChatMessage, type DirectMessage } from '@hcengineering/chunter'
import { type Resources } from '@hcengineering/platform'
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 notification, { type DocNotifyContext, notificationId } from '@hcengineering/notification'
import ChannelPresenter from './components/ChannelPresenter.svelte'
import ChannelView from './components/ChannelView.svelte'
import ChannelPanel from './components/ChannelPanel.svelte'
import ChunterBrowser from './components/chat/specials/ChunterBrowser.svelte'
import ConvertDmToPrivateChannelModal from './components/ConvertDmToPrivateChannel.svelte'
@ -60,19 +57,15 @@ import {
ChannelTitleProvider,
DirectTitleProvider,
canDeleteMessage,
chunterSpaceLinkFragmentProvider,
dmIdentifierProvider,
getDmName,
getMessageLink,
getTitle,
getUnreadThreadsCount,
canCopyMessageLink,
buildThreadLink,
getThreadLink,
leaveChannelAction,
removeChannelAction
} 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 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 async function chunterBrowserVisible (): Promise<boolean> {
@ -176,34 +141,7 @@ export async function deleteChatMessage (message: ChatMessage): Promise<void> {
await client.remove(message)
}
export async function replyToThread (message: ActivityMessage): Promise<void> {
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 { replyToThread } from './navigation'
export default async (): Promise<Resources> => ({
filter: {
@ -215,7 +153,6 @@ export default async (): Promise<Resources> => ({
ThreadParentPresenter,
ThreadViewPanel,
ChannelHeader,
ChannelView,
ChannelPanel,
ChannelPresenter,
DirectMessagePresenter,
@ -262,7 +199,6 @@ export default async (): Promise<Resources> => ({
UnarchiveChannel,
ConvertDmToPrivateChannel,
DeleteChatMessage: deleteChatMessage,
OpenChannel: openChannel,
LeaveChannel: leaveChannelAction,
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,
SubscribeComment: '' as ViewAction,
UnsubscribeComment: '' as ViewAction,
OpenChannel: '' as ViewAction,
LeaveChannel: '' as ViewAction,
RemoveChannel: '' as ViewAction
},

View File

@ -12,14 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
type Channel,
type ChatMessage,
chunterId,
type ChunterSpace,
type DirectMessage,
type ThreadMessage
} from '@hcengineering/chunter'
import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage } from '@hcengineering/chunter'
import contact, { type Employee, type PersonAccount, getName, type Person } from '@hcengineering/contact'
import { employeeByIdStore, PersonIcon } from '@hcengineering/contact-resources'
import {
@ -35,14 +28,7 @@ import {
generateId
} from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import {
type AnySvelteComponent,
getCurrentResolvedLocation,
getLocation,
type Location,
navigate
} from '@hcengineering/ui'
import { workbenchId } from '@hcengineering/workbench'
import { type AnySvelteComponent } from '@hcengineering/ui'
import { type Asset, getResource, translate } from '@hcengineering/platform'
import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
import activity, {
@ -57,13 +43,12 @@ import {
InboxNotificationsClientImpl,
isMentionNotification
} 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 chunter from './plugin'
import DirectIcon from './components/DirectIcon.svelte'
import ChannelIcon from './components/ChannelIcon.svelte'
import { chatSpecials } from './components/chat/utils'
export async function getDmName (client: Client, space?: Space): Promise<string> {
if (space === undefined) {
@ -205,40 +190,6 @@ export async function ChannelTitleProvider (client: Client, id: Ref<Channel>): P
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 {
Messages,
Channels,
@ -246,28 +197,6 @@ export enum SearchType {
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> {
const client = getClient()
const hierarchy = client.getHierarchy()
@ -281,24 +210,6 @@ export async function getTitle (doc: Doc): Promise<string> {
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 {
const client = getClient()
const hierarchy = client.getHierarchy()
@ -387,54 +298,6 @@ export async function filterChatMessages (
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> {
const client = getClient()
@ -533,3 +396,7 @@ export async function removeChannelAction (context?: DocNotifyContext): Promise<
await deleteContextNotifications(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: {
DmHeader: '' as AnyComponent,
ChannelView: '' as AnyComponent,
ThreadView: '' as AnyComponent,
Thread: '' as AnyComponent,
Reactions: '' as AnyComponent,
@ -211,7 +210,6 @@ export default plugin(chunterId, {
},
action: {
DeleteChatMessage: '' as Ref<Action>,
OpenChannel: '' as Ref<Action>,
LeaveChannel: '' as Ref<Action>,
RemoveChannel: '' as Ref<Action>,
CloseConversation: '' as Ref<Action>

View File

@ -45,6 +45,7 @@
import Filter from '../Filter.svelte'
import {
archiveAll,
decodeObjectURI,
getDisplayInboxData,
isMentionNotification,
openInboxDoc,
@ -65,6 +66,7 @@
const inboxClient = InboxNotificationsClientImpl.getClient()
const notificationsByContextStore = inboxClient.inboxNotificationsByContext
const contextByIdStore = inboxClient.contextById
const contextByDocStore = inboxClient.contextByDoc
const contextsStore = inboxClient.contexts
const allTab: TabItem = {
@ -98,8 +100,10 @@
async function syncLocation (newLocation: Location): Promise<void> {
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) {
selectedContext = undefined
@ -179,7 +183,8 @@
const selectedMsg = selectedNotification.mentionedIn as Ref<ActivityMessage>
openInboxDoc(
selectedContext._id,
selectedContext.attachedTo,
selectedContext.attachedToClass,
isActivityMessageClass(selectedContext.attachedToClass)
? (selectedContext.attachedTo as Ref<ActivityMessage>)
: undefined,
@ -192,20 +197,31 @@
const thread = await client.findOne(chunter.class.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)) {
openInboxDoc(selectedContext._id, undefined, selectedContext.attachedTo as Ref<ActivityMessage>)
openInboxDoc(
selectedContext.attachedTo,
selectedContext.attachedToClass,
undefined,
selectedContext.attachedTo as Ref<ActivityMessage>
)
} else {
const selectedMsg = (selectedNotification as ActivityInboxNotification)?.attachedTo
openInboxDoc(
selectedContext._id,
selectedContext.attachedTo,
selectedContext.attachedToClass,
selectedMsg ? (selectedContext.attachedTo as Ref<ActivityMessage>) : undefined,
selectedMsg ?? (selectedContext.attachedTo as Ref<ActivityMessage>)
)
}
} 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
}
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 {
loc: {
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 (
loc: Location,
contextId: Ref<DocNotifyContext>
_id: Ref<Doc>,
_class: Ref<Class<Doc>>
): Promise<ResolvedLocation | undefined> {
const client = getClient()
@ -437,35 +438,18 @@ async function generateLocation (
const workspace = loc.path[1] ?? ''
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 =
threadId !== undefined ? await client.findOne(activity.class.ActivityMessage, { _id: threadId }) : undefined
if (thread === undefined) {
return {
loc: {
path: [appComponent, workspace, notificationId, contextId],
path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class)],
fragment: undefined,
query: { ...loc.query }
},
defaultLocation: {
path: [appComponent, workspace, notificationId, contextId],
path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class)],
fragment: undefined,
query: { ...loc.query }
}
@ -474,20 +458,29 @@ async function generateLocation (
return {
loc: {
path: [appComponent, workspace, notificationId, contextId, threadId as string],
path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class), threadId as string],
fragment: undefined,
query: { ...loc.query }
},
defaultLocation: {
path: [appComponent, workspace, notificationId, contextId, threadId as string],
path: [appComponent, workspace, notificationId, encodeObjectURI(_id, _class), threadId as string],
fragment: undefined,
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 (
contextId?: Ref<DocNotifyContext>,
_id?: Ref<Doc>,
_class?: Ref<Class<Doc>>,
thread?: Ref<ActivityMessage>,
message?: Ref<ActivityMessage>
): void {
@ -497,14 +490,14 @@ export function openInboxDoc (
return
}
if (contextId === undefined) {
loc.query = { ...loc.query, message: null }
if (_id === undefined || _class === undefined) {
loc.query = { message: null }
loc.path.length = 3
navigate(loc)
return
}
loc.path[3] = contextId
loc.path[3] = encodeObjectURI(_id, _class)
if (thread !== undefined) {
loc.path[4] = thread