mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-24 06:43:16 +03:00
UBERF-7239: Support short/custom links in inbox/chat/planner (#5815)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
61a0833ec4
commit
1e9fea356e
@ -301,6 +301,11 @@ function defineDocument (builder: Builder): void {
|
||||
encode: document.function.GetObjectLinkFragment
|
||||
})
|
||||
|
||||
builder.mixin(document.class.Document, core.class.Class, view.mixin.LinkIdProvider, {
|
||||
encode: document.function.GetDocumentLinkId,
|
||||
decode: document.function.ParseDocumentId
|
||||
})
|
||||
|
||||
builder.mixin(document.class.Document, core.class.Class, view.mixin.ObjectIcon, {
|
||||
component: document.component.DocumentIcon
|
||||
})
|
||||
|
@ -932,6 +932,26 @@ export function createModel (builder: Builder): void {
|
||||
encode: recruit.function.GetIdObjectLinkFragment
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.LinkIdProvider, {
|
||||
encode: recruit.function.IdProvider,
|
||||
decode: recruit.function.ParseLinkId
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.LinkIdProvider, {
|
||||
encode: recruit.function.IdProvider,
|
||||
decode: recruit.function.ParseLinkId
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.LinkIdProvider, {
|
||||
encode: recruit.function.IdProvider,
|
||||
decode: recruit.function.ParseLinkId
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.LinkIdProvider, {
|
||||
encode: recruit.function.IdProvider,
|
||||
decode: recruit.function.ParseLinkId
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
|
@ -58,7 +58,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
GetTalentId: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||
HideDoneState: '' as ViewQueryAction,
|
||||
HideArchivedVacancies: '' as ViewQueryAction,
|
||||
ApplicantHasEmail: '' as Resource<ViewActionAvailabilityFunction>
|
||||
ApplicantHasEmail: '' as Resource<ViewActionAvailabilityFunction>,
|
||||
ParseLinkId: '' as Resource<(id: string) => Promise<Ref<Doc> | undefined>>
|
||||
},
|
||||
string: {
|
||||
ApplicationsShort: '' as IntlString,
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/document": "^0.6.0",
|
||||
"@hcengineering/server-document": "^0.6.0"
|
||||
"@hcengineering/server-document": "^0.6.0",
|
||||
"@hcengineering/server-view": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import document from '@hcengineering/document'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverDocument from '@hcengineering/server-document'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
|
||||
export { serverDocumentId } from '@hcengineering/server-document'
|
||||
|
||||
@ -22,6 +23,10 @@ export function createModel (builder: Builder): void {
|
||||
presenter: serverDocument.function.DocumentTextPresenter
|
||||
})
|
||||
|
||||
builder.mixin(document.class.Document, core.class.Class, serverView.mixin.ServerLinkIdProvider, {
|
||||
encode: serverDocument.function.DocumentLinkIdProvider
|
||||
})
|
||||
|
||||
builder.mixin(document.class.Document, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||
searchConfig: {
|
||||
iconConfig: {
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/server-notification": "^0.6.1"
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-view": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import serverNotification from '@hcengineering/server-notification'
|
||||
import serverRecruit from '@hcengineering/server-recruit'
|
||||
import serverContact from '@hcengineering/server-contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
|
||||
export { serverRecruitId } from '@hcengineering/server-recruit'
|
||||
|
||||
@ -43,6 +44,22 @@ export function createModel (builder: Builder): void {
|
||||
presenter: serverRecruit.function.VacancyTextPresenter
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, serverView.mixin.ServerLinkIdProvider, {
|
||||
encode: serverRecruit.function.LinkIdProvider
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Opinion, core.class.Class, serverView.mixin.ServerLinkIdProvider, {
|
||||
encode: serverRecruit.function.LinkIdProvider
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Review, core.class.Class, serverView.mixin.ServerLinkIdProvider, {
|
||||
encode: serverRecruit.function.LinkIdProvider
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Vacancy, core.class.Class, serverView.mixin.ServerLinkIdProvider, {
|
||||
encode: serverRecruit.function.LinkIdProvider
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverRecruit.trigger.OnRecruitUpdate
|
||||
})
|
||||
|
@ -36,6 +36,8 @@
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/model-tracker": "^0.6.0",
|
||||
"@hcengineering/server-tracker": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24"
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/server-view": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import serverCore from '@hcengineering/server-core'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import serverTracker from '@hcengineering/server-tracker'
|
||||
import contact from '@hcengineering/contact'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
|
||||
export { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
|
||||
@ -37,6 +38,10 @@ export function createModel (builder: Builder): void {
|
||||
presenter: serverTracker.function.IssueNotificationContentProvider
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, serverView.mixin.ServerLinkIdProvider, {
|
||||
encode: serverTracker.function.IssueLinkIdProvider
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||
searchConfig: {
|
||||
iconConfig: {
|
||||
|
@ -30,8 +30,9 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-view": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-view": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,23 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core from '@hcengineering/core'
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
import core, { type Doc } from '@hcengineering/core'
|
||||
import { type Builder, Mixin } from '@hcengineering/model'
|
||||
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
||||
import serverView, { type ServerLinkIdProvider } from '@hcengineering/server-view'
|
||||
import { TClass } from '@hcengineering/model-core'
|
||||
import { type Resource } from '@hcengineering/platform'
|
||||
|
||||
export { serverViewId } from '@hcengineering/server-view'
|
||||
|
||||
@Mixin(serverView.mixin.ServerLinkIdProvider, core.class.Class)
|
||||
export class TServerLinkIdProvider extends TClass implements ServerLinkIdProvider {
|
||||
encode!: Resource<(doc: Doc, control: TriggerControl) => Promise<string>>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TServerLinkIdProvider)
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverView.trigger.OnCustomAttributeRemove
|
||||
})
|
||||
|
@ -438,6 +438,11 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(tracker.class.Component, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||
builder.mixin(tracker.class.IssueTemplate, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.LinkIdProvider, {
|
||||
encode: tracker.function.GetIssueId,
|
||||
decode: tracker.function.GetIssueIdByIdentifier
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.ActivityMessageControl, core.space.Model, {
|
||||
objectClass: tracker.class.Issue,
|
||||
skip: [
|
||||
|
@ -90,7 +90,8 @@ import {
|
||||
type ObjectIcon,
|
||||
type ObjectTooltip,
|
||||
type AttrPresenter,
|
||||
type AttributeCategory
|
||||
type AttributeCategory,
|
||||
type LinkIdProvider
|
||||
} from '@hcengineering/view'
|
||||
|
||||
import view from './plugin'
|
||||
@ -352,6 +353,12 @@ export class TLinkProvider extends TClass implements LinkProvider {
|
||||
encode!: Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.LinkIdProvider, core.class.Class)
|
||||
export class TLinkIdProvider extends TClass implements LinkIdProvider {
|
||||
encode!: Resource<(doc: Doc) => Promise<string>>
|
||||
decode!: Resource<(id: string) => Promise<Ref<Doc> | undefined>>
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.ObjectPanel, core.class.Class)
|
||||
export class TObjectPanel extends TClass implements ObjectPanel {
|
||||
component!: AnyComponent
|
||||
@ -450,7 +457,8 @@ export function createModel (builder: Builder): void {
|
||||
TObjectIdentifier,
|
||||
TObjectTooltip,
|
||||
TObjectIcon,
|
||||
TAttrPresenter
|
||||
TAttrPresenter,
|
||||
TLinkIdProvider
|
||||
)
|
||||
|
||||
classPresenter(
|
||||
|
@ -53,12 +53,12 @@ export function openPanel (
|
||||
})
|
||||
}
|
||||
|
||||
export function closePanel (shoulRedirect: boolean = true): void {
|
||||
export function closePanel (shouldRedirect: boolean = true): void {
|
||||
currentLocation = undefined
|
||||
panelstore.update(() => {
|
||||
return { panel: undefined }
|
||||
})
|
||||
if (shoulRedirect) {
|
||||
if (shouldRedirect) {
|
||||
const loc = getLocation()
|
||||
loc.fragment = undefined
|
||||
navigate(loc)
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Doc, Ref, Class } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Component,
|
||||
defineSeparators,
|
||||
@ -26,29 +26,32 @@
|
||||
restoreLocation,
|
||||
deviceOptionsStore as deviceInfo
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
import { NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { onMount } from 'svelte'
|
||||
import { chunterId } from '@hcengineering/chunter'
|
||||
import { ActivityMessage } from '@hcengineering/activity'
|
||||
import view, { decodeObjectURI } from '@hcengineering/view'
|
||||
import { parseLinkId, getObjectLinkId } from '@hcengineering/view-resources'
|
||||
|
||||
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'
|
||||
import { openChannel } from '../../navigation'
|
||||
|
||||
const notificationsClient = InboxNotificationsClientImpl.getClient()
|
||||
const contextByDocStore = notificationsClient.contextByDoc
|
||||
const objectQuery = createQuery()
|
||||
const client = getClient()
|
||||
|
||||
const navigatorModel: NavigatorModel = {
|
||||
spaces: [],
|
||||
specials: chatSpecials
|
||||
}
|
||||
|
||||
let selectedData: { _id: Ref<Doc>, _class: Ref<Class<Doc>> } | undefined = undefined
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
let selectedData: { id: string, _class: Ref<Class<Doc>> } | undefined = undefined
|
||||
|
||||
let currentSpecial: SpecialNavModel | undefined
|
||||
|
||||
@ -58,10 +61,18 @@
|
||||
syncLocation(loc)
|
||||
})
|
||||
|
||||
$: void loadObject(selectedData?._id, selectedData?._class)
|
||||
$: void loadObject(selectedData?.id, selectedData?._class)
|
||||
|
||||
async function loadObject (_id?: Ref<Doc>, _class?: Ref<Class<Doc>>): Promise<void> {
|
||||
if (_id == null || _class == null || _class === '') {
|
||||
async function loadObject (id?: string, _class?: Ref<Class<Doc>>): Promise<void> {
|
||||
if (id == null || _class == null || _class === '') {
|
||||
object = undefined
|
||||
objectQuery.unsubscribe()
|
||||
return
|
||||
}
|
||||
|
||||
const _id: Ref<Doc> | undefined = await parseLinkId(linkProviders, id, _class)
|
||||
|
||||
if (_id === undefined) {
|
||||
object = undefined
|
||||
objectQuery.unsubscribe()
|
||||
return
|
||||
@ -84,7 +95,7 @@
|
||||
|
||||
const id = loc.path[3]
|
||||
|
||||
if (!id) {
|
||||
if (id == null || id === '') {
|
||||
currentSpecial = undefined
|
||||
selectedData = undefined
|
||||
object = undefined
|
||||
@ -98,26 +109,30 @@
|
||||
selectedData = undefined
|
||||
object = undefined
|
||||
} else {
|
||||
const [_id, _class] = decodeChannelURI(loc.path[3])
|
||||
selectedData = { _id, _class }
|
||||
const [id, _class] = decodeObjectURI(loc.path[3])
|
||||
selectedData = { id, _class }
|
||||
}
|
||||
}
|
||||
|
||||
function handleChannelSelected (event: CustomEvent): void {
|
||||
async function handleChannelSelected (event: CustomEvent): Promise<void> {
|
||||
if (event.detail === null) {
|
||||
selectedData = undefined
|
||||
return
|
||||
}
|
||||
|
||||
const detail = (event.detail ?? {}) as SelectChannelEvent
|
||||
const _class = detail.object._class
|
||||
const _id = detail.object._id
|
||||
|
||||
selectedData = { _id: detail.object._id, _class: detail.object._class }
|
||||
const id = await getObjectLinkId(linkProviders, _id, _class, detail.object)
|
||||
|
||||
if (selectedData._id !== object?._id) {
|
||||
selectedData = { id, _class }
|
||||
|
||||
if (_id !== object?._id) {
|
||||
object = detail.object
|
||||
}
|
||||
|
||||
openChannel(selectedData._id, selectedData._class)
|
||||
openChannel(selectedData.id, selectedData._class)
|
||||
}
|
||||
|
||||
defineSeparators('chat', [
|
||||
@ -134,7 +149,7 @@
|
||||
{#if $deviceInfo.navigator.visible}
|
||||
<div class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal' ? 'portrait' : 'landscape'}">
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<ChatNavigator objectId={selectedData?._id} {object} {currentSpecial} on:select={handleChannelSelected} />
|
||||
<ChatNavigator {object} {currentSpecial} on:select={handleChannelSelected} />
|
||||
</div>
|
||||
<Separator name="chat" float={$deviceInfo.navigator.float ? 'navigator' : true} index={0} />
|
||||
</div>
|
||||
|
@ -25,7 +25,6 @@
|
||||
import ChatNavSection from './ChatNavSection.svelte'
|
||||
import chunter from '../../../plugin'
|
||||
|
||||
export let objectId: Ref<Doc> | undefined
|
||||
export let object: Doc | undefined
|
||||
export let model: ChatNavGroupModel
|
||||
|
||||
@ -191,7 +190,7 @@
|
||||
id={section.id}
|
||||
objects={section.objects}
|
||||
{contexts}
|
||||
{objectId}
|
||||
objectId={object?._id}
|
||||
header={section.label}
|
||||
actions={getSectionActions(section, contexts)}
|
||||
sortFn={model.sortFn}
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import { getActions } from '@hcengineering/view-resources'
|
||||
import { getActions, getObjectLinkId } from '@hcengineering/view-resources'
|
||||
import {
|
||||
getNotificationsCount,
|
||||
InboxNotificationsClientImpl,
|
||||
@ -68,6 +68,8 @@
|
||||
actions = res
|
||||
})
|
||||
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
async function getChannelActions (context: DocNotifyContext | undefined, object: Doc): Promise<Action[]> {
|
||||
const result: Action[] = []
|
||||
|
||||
@ -79,7 +81,8 @@
|
||||
icon: view.icon.Open,
|
||||
label: view.string.Open,
|
||||
action: async () => {
|
||||
openChannel(object._id, object._class)
|
||||
const id = await getObjectLinkId(linkProviders, object._id, object._class, object)
|
||||
openChannel(id, object._class)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -29,7 +29,6 @@
|
||||
import { userSearch } from '../../../index'
|
||||
import { navigateToSpecial } from '../../../navigation'
|
||||
|
||||
export let objectId: Ref<Doc> | undefined
|
||||
export let object: Doc | undefined
|
||||
export let currentSpecial: SpecialNavModel | undefined
|
||||
|
||||
@ -112,7 +111,7 @@
|
||||
</div>
|
||||
<Scroller shrink>
|
||||
{#each chatNavGroupModels as model}
|
||||
<ChatNavGroup {object} {objectId} {model} on:select />
|
||||
<ChatNavGroup {object} {model} on:select />
|
||||
{/each}
|
||||
</Scroller>
|
||||
<NavFooter />
|
||||
|
@ -4,22 +4,16 @@ 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 { getObjectLinkId } from '@hcengineering/view-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import view, { encodeObjectURI, decodeObjectURI } from '@hcengineering/view'
|
||||
|
||||
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 [_id, _class].join('|')
|
||||
}
|
||||
|
||||
export function openChannel (_id: Ref<Doc>, _class: Ref<Class<Doc>>, thread?: Ref<ActivityMessage>): void {
|
||||
export function openChannel (_id: string, _class: Ref<Class<Doc>>, thread?: Ref<ActivityMessage>): void {
|
||||
const loc = getCurrentLocation()
|
||||
|
||||
const id = encodeChannelURI(_id, _class)
|
||||
const id = encodeObjectURI(_id, _class)
|
||||
|
||||
if (loc.path[3] === id) {
|
||||
return
|
||||
@ -45,12 +39,18 @@ export async function openMessageFromSpecial (message?: ActivityMessage): Promis
|
||||
}
|
||||
|
||||
const loc = getCurrentResolvedLocation()
|
||||
const client = getClient()
|
||||
const providers = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
if (isThreadMessage(message)) {
|
||||
loc.path[3] = encodeChannelURI(message.objectId, message.objectClass)
|
||||
const id = await getObjectLinkId(providers, message.objectId, message.objectClass)
|
||||
|
||||
loc.path[3] = encodeObjectURI(id, message.objectClass)
|
||||
loc.path[4] = message.attachedTo
|
||||
} else {
|
||||
loc.path[3] = encodeChannelURI(message.attachedTo, message.attachedToClass)
|
||||
const id = await getObjectLinkId(providers, message.attachedTo, message.attachedToClass)
|
||||
|
||||
loc.path[3] = encodeObjectURI(id, message.attachedToClass)
|
||||
}
|
||||
|
||||
loc.query = { ...loc.query, message: message._id }
|
||||
@ -66,47 +66,58 @@ export function navigateToSpecial (specialId: string): void {
|
||||
}
|
||||
|
||||
export async function getMessageLink (message: ActivityMessage): Promise<string> {
|
||||
const client = getClient()
|
||||
const location = getCurrentResolvedLocation()
|
||||
const providers = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
let threadParent = ''
|
||||
let _id: Ref<Doc>
|
||||
let _id: string
|
||||
let _class: Ref<Class<Doc>>
|
||||
|
||||
if (isThreadMessage(message)) {
|
||||
threadParent = `/${message.attachedTo}`
|
||||
_id = message.objectId
|
||||
_id = await getObjectLinkId(providers, message.objectId, message.objectClass)
|
||||
_class = message.objectClass
|
||||
} else {
|
||||
_id = message.attachedTo
|
||||
_id = await getObjectLinkId(providers, message.attachedTo, message.attachedToClass)
|
||||
_class = message.attachedToClass
|
||||
}
|
||||
|
||||
const id = encodeChannelURI(_id, _class)
|
||||
const id = encodeObjectURI(_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()
|
||||
const client = getClient()
|
||||
const providers = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
const id = await getObjectLinkId(providers, doc._id, doc._class, doc)
|
||||
|
||||
loc.path.length = 2
|
||||
loc.fragment = undefined
|
||||
loc.query = undefined
|
||||
loc.path[2] = chunterId
|
||||
loc.path[3] = encodeChannelURI(doc._id, doc._class)
|
||||
loc.path[3] = encodeObjectURI(id, doc._class)
|
||||
|
||||
return loc
|
||||
}
|
||||
|
||||
export function buildThreadLink (
|
||||
export async function buildThreadLink (
|
||||
loc: Location,
|
||||
_id: Ref<Doc>,
|
||||
_class: Ref<Class<Doc>>,
|
||||
threadParent: Ref<ActivityMessage>
|
||||
): Location {
|
||||
threadParent: Ref<ActivityMessage>,
|
||||
doc?: Doc
|
||||
): Promise<Location> {
|
||||
const client = getClient()
|
||||
const providers = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
const id = await getObjectLinkId(providers, _id, _class, doc)
|
||||
|
||||
const specials = chatSpecials.map(({ id }) => id)
|
||||
const id = encodeChannelURI(_id, _class)
|
||||
const isSameChannel = loc.path[3] === id
|
||||
const objectURI = encodeObjectURI(id, _class)
|
||||
const isSameChannel = loc.path[3] === objectURI
|
||||
|
||||
if (!isSameChannel) {
|
||||
loc.query = { message: threadParent }
|
||||
@ -121,7 +132,7 @@ export function buildThreadLink (
|
||||
loc.path[2] = chunterId
|
||||
}
|
||||
|
||||
loc.path[3] = id
|
||||
loc.path[3] = objectURI
|
||||
loc.path[4] = threadParent
|
||||
loc.fragment = undefined
|
||||
|
||||
@ -131,7 +142,7 @@ export function buildThreadLink (
|
||||
export async function getThreadLink (doc: ThreadMessage): Promise<Location> {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
|
||||
return buildThreadLink(loc, doc.objectId, doc.objectClass, doc.attachedTo)
|
||||
return await buildThreadLink(loc, doc.objectId, doc.objectClass, doc.attachedTo, doc)
|
||||
}
|
||||
|
||||
export async function replyToThread (message: ActivityMessage): Promise<void> {
|
||||
@ -141,11 +152,37 @@ export async function replyToThread (message: ActivityMessage): Promise<void> {
|
||||
loc.path[2] = chunterId
|
||||
}
|
||||
|
||||
navigate(buildThreadLink(loc, message.attachedTo, message.attachedToClass, message._id))
|
||||
const newLoc = await buildThreadLink(loc, message.attachedTo, message.attachedToClass, message._id)
|
||||
|
||||
navigate(newLoc)
|
||||
}
|
||||
|
||||
export async function getMessageLocation (doc: ActivityMessage): Promise<Location> {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
|
||||
return buildThreadLink(loc, doc.attachedTo, doc.attachedToClass, doc._id)
|
||||
return await buildThreadLink(loc, doc.attachedTo, doc.attachedToClass, doc._id)
|
||||
}
|
||||
|
||||
export async function resetChunterLocIfEqual (_id: Ref<Doc>, _class: Ref<Class<Doc>>, doc?: Doc): Promise<void> {
|
||||
const loc = getCurrentLocation()
|
||||
|
||||
if (loc.path[2] !== chunterId) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const providers = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
const id = await getObjectLinkId(providers, _id, _class, doc)
|
||||
|
||||
const [locId] = decodeObjectURI(loc.path[3])
|
||||
|
||||
if (locId !== id) {
|
||||
return
|
||||
}
|
||||
|
||||
loc.path[3] = ''
|
||||
loc.path[4] = ''
|
||||
loc.query = {}
|
||||
loc.path.length = 3
|
||||
navigate(loc)
|
||||
}
|
||||
|
@ -12,13 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import {
|
||||
type Channel,
|
||||
type ChatMessage,
|
||||
chunterId,
|
||||
type DirectMessage,
|
||||
type ThreadMessage
|
||||
} from '@hcengineering/chunter'
|
||||
import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage } from '@hcengineering/chunter'
|
||||
import contact, { type Employee, getName, type Person, type PersonAccount } from '@hcengineering/contact'
|
||||
import { employeeByIdStore, PersonIcon } from '@hcengineering/contact-resources'
|
||||
import {
|
||||
@ -34,7 +28,7 @@ import {
|
||||
type Timestamp
|
||||
} from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { type AnySvelteComponent, getCurrentLocation, navigate } from '@hcengineering/ui'
|
||||
import { type AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { type Asset, translate } from '@hcengineering/platform'
|
||||
import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
|
||||
import activity, {
|
||||
@ -55,7 +49,7 @@ import { get, type Unsubscriber } from 'svelte/store'
|
||||
import chunter from './plugin'
|
||||
import DirectIcon from './components/DirectIcon.svelte'
|
||||
import ChannelIcon from './components/ChannelIcon.svelte'
|
||||
import { decodeChannelURI } from './navigation'
|
||||
import { resetChunterLocIfEqual } from './navigation'
|
||||
|
||||
export async function getDmName (client: Client, space?: Space): Promise<string> {
|
||||
if (space === undefined) {
|
||||
@ -350,7 +344,7 @@ export async function leaveChannel (channel: Channel, value: Ref<Account> | Arra
|
||||
const context = await client.findOne(notification.class.DocNotifyContext, { attachedTo: channel._id })
|
||||
|
||||
await client.update(channel, { $pull: { members: value } })
|
||||
await removeChannelAction(context)
|
||||
await removeChannelAction(context, undefined, { object: channel })
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,37 +396,31 @@ export async function readChannelMessages (
|
||||
}
|
||||
}
|
||||
|
||||
function resetChunterLoc (objectId: Ref<Doc>): void {
|
||||
const loc = getCurrentLocation()
|
||||
const [_id] = decodeChannelURI(loc.path[3])
|
||||
|
||||
if (loc.path[2] !== chunterId || _id !== objectId) {
|
||||
return
|
||||
}
|
||||
|
||||
loc.path[3] = ''
|
||||
loc.path[4] = ''
|
||||
loc.query = {}
|
||||
loc.path.length = 3
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export async function leaveChannelAction (context?: DocNotifyContext): Promise<void> {
|
||||
export async function leaveChannelAction (
|
||||
context?: DocNotifyContext,
|
||||
_?: Event,
|
||||
props?: { object?: Channel }
|
||||
): Promise<void> {
|
||||
if (context === undefined) {
|
||||
return
|
||||
}
|
||||
const client = getClient()
|
||||
const channel = await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> })
|
||||
const channel =
|
||||
props?.object ?? (await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> }))
|
||||
|
||||
if (channel === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await leaveChannel(channel, getCurrentAccount()._id)
|
||||
resetChunterLoc(channel._id)
|
||||
await resetChunterLocIfEqual(channel._id, channel._class, channel)
|
||||
}
|
||||
|
||||
export async function removeChannelAction (context?: DocNotifyContext): Promise<void> {
|
||||
export async function removeChannelAction (
|
||||
context?: DocNotifyContext,
|
||||
_?: Event,
|
||||
props?: { object?: Doc }
|
||||
): Promise<void> {
|
||||
if (context === undefined) {
|
||||
return
|
||||
}
|
||||
@ -442,7 +430,7 @@ export async function removeChannelAction (context?: DocNotifyContext): Promise<
|
||||
await archiveContextNotifications(context)
|
||||
await client.remove(context)
|
||||
|
||||
resetChunterLoc(context.attachedTo)
|
||||
await resetChunterLocIfEqual(context.attachedTo, context.attachedToClass, props?.object)
|
||||
}
|
||||
|
||||
export function isThreadMessage (message: ActivityMessage): message is ThreadMessage {
|
||||
|
@ -46,7 +46,15 @@ import TeamspaceSpacePresenter from './components/navigator/TeamspaceSpacePresen
|
||||
import CreateTeamspace from './components/teamspace/CreateTeamspace.svelte'
|
||||
|
||||
import document from './plugin'
|
||||
import { createEmptyDocument, documentTitleProvider, getDocumentLink, getDocumentUrl, resolveLocation } from './utils'
|
||||
import {
|
||||
createEmptyDocument,
|
||||
documentTitleProvider,
|
||||
getDocumentLink,
|
||||
getDocumentLinkId,
|
||||
getDocumentUrl,
|
||||
parseDocumentId,
|
||||
resolveLocation
|
||||
} from './utils'
|
||||
|
||||
const toObjectSearchResult = (e: WithLookup<Document>): ObjectSearchResult => ({
|
||||
doc: e,
|
||||
@ -187,7 +195,9 @@ export default async (): Promise<Resources> => ({
|
||||
GetObjectLinkFragment: getDocumentLink,
|
||||
DocumentTitleProvider: documentTitleProvider,
|
||||
CanLockDocument: canLockDocument,
|
||||
CanUnlockDocument: canUnlockDocument
|
||||
CanUnlockDocument: canUnlockDocument,
|
||||
GetDocumentLinkId: getDocumentLinkId,
|
||||
ParseDocumentId: parseDocumentId
|
||||
},
|
||||
resolver: {
|
||||
Location: resolveLocation
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import { type Client, type Doc, type Ref } from '@hcengineering/core'
|
||||
import document, { documentId } from '@hcengineering/document'
|
||||
import document, { type Document, documentId } from '@hcengineering/document'
|
||||
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type AnyComponent, type Location } from '@hcengineering/ui'
|
||||
|
||||
@ -29,7 +29,9 @@ export default mergeIds(documentId, document, {
|
||||
function: {
|
||||
DocumentTitleProvider: '' as Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>,
|
||||
GetDocumentLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||
GetObjectLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
|
||||
GetObjectLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
|
||||
GetDocumentLinkId: '' as Resource<(doc: Doc) => Promise<string>>,
|
||||
ParseDocumentId: '' as Resource<(id: string) => Promise<Ref<Document> | undefined>>
|
||||
},
|
||||
string: {
|
||||
DocumentNamePlaceholder: '' as IntlString,
|
||||
|
@ -105,12 +105,17 @@ export async function generateLocation (loc: Location, id: Ref<Document>): Promi
|
||||
}
|
||||
|
||||
export function getDocumentIdFromFragment (fragment: string): Ref<Document> | undefined {
|
||||
const [, _id] = decodeURIComponent(fragment).split('|')
|
||||
return _id as Ref<Document>
|
||||
const [, id] = decodeURIComponent(fragment).split('|')
|
||||
|
||||
if (id == null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return (parseDocumentId(id) ?? id) as Ref<Document>
|
||||
}
|
||||
|
||||
export function getDocumentUrl (doc: Document): string {
|
||||
const id = getDocumentId(doc)
|
||||
const id = getDocumentLinkId(doc)
|
||||
|
||||
const location = getCurrentResolvedLocation()
|
||||
const frontUrl = getMetadata(presentation.metadata.FrontUrl)
|
||||
@ -124,17 +129,17 @@ export function getDocumentLink (doc: Document): Location {
|
||||
loc.fragment = undefined
|
||||
loc.query = undefined
|
||||
loc.path[2] = documentId
|
||||
loc.path[3] = getDocumentId(doc)
|
||||
loc.path[3] = getDocumentLinkId(doc)
|
||||
|
||||
return loc
|
||||
}
|
||||
|
||||
function getDocumentId (doc: Document): string {
|
||||
export function getDocumentLinkId (doc: Document): string {
|
||||
const slug = slugify(doc.name, { lower: true })
|
||||
return `${slug}-${doc._id}`
|
||||
}
|
||||
|
||||
function parseDocumentId (shortLink: string): Ref<Document> | undefined {
|
||||
export function parseDocumentId (shortLink: string): Ref<Document> | undefined {
|
||||
const parts = shortLink.split('-')
|
||||
if (parts.length > 1) {
|
||||
return parts[parts.length - 1] as Ref<Document>
|
||||
|
@ -39,7 +39,7 @@
|
||||
deviceOptionsStore as deviceInfo
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ListSelectionProvider, restrictionStore, updateFocus } from '@hcengineering/view-resources'
|
||||
import { ListSelectionProvider, parseLinkId, restrictionStore, updateFocus } from '@hcengineering/view-resources'
|
||||
import workbench, { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@hcengineering/workbench'
|
||||
import { SpaceView, buildNavModel } from '@hcengineering/workbench-resources'
|
||||
import guest from '../plugin'
|
||||
@ -189,11 +189,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
async function setOpenPanelFocus (fragment: string): Promise<void> {
|
||||
const props = decodeURIComponent(fragment).split('|')
|
||||
|
||||
if (props.length >= 3) {
|
||||
const doc = await client.findOne<Doc>(props[2] as Ref<Class<Doc>>, { _id: props[1] as Ref<Doc> })
|
||||
const id = props[1]
|
||||
const _class = props[2] as Ref<Class<Doc>>
|
||||
const _id = await parseLinkId(linkProviders, id, _class)
|
||||
|
||||
const doc = await client.findOne<Doc>(_class, { _id })
|
||||
if (doc !== undefined) {
|
||||
await checkAccess(doc)
|
||||
const provider = ListSelectionProvider.Find(doc._id)
|
||||
@ -203,8 +209,8 @@
|
||||
})
|
||||
openPanel(
|
||||
props[0] as AnyComponent,
|
||||
props[1],
|
||||
props[2],
|
||||
_id,
|
||||
_class,
|
||||
(props[3] ?? undefined) as PopupAlignment,
|
||||
(props[4] ?? undefined) as AnyComponent
|
||||
)
|
||||
@ -253,14 +259,15 @@
|
||||
async function getWindowTitle (loc: Location): Promise<string | undefined> {
|
||||
if (loc.fragment == null) return
|
||||
const hierarchy = client.getHierarchy()
|
||||
const [, _id, _class] = decodeURIComponent(loc.fragment).split('|')
|
||||
const [, id, _class] = decodeURIComponent(loc.fragment).split('|')
|
||||
if (_class == null) return
|
||||
|
||||
const mixin = hierarchy.classHierarchyMixin(_class as Ref<Class<Doc>>, view.mixin.ObjectTitle)
|
||||
if (mixin === undefined) return
|
||||
const titleProvider = await getResource(mixin.titleProvider)
|
||||
try {
|
||||
return await titleProvider(client, _id as Ref<Doc>)
|
||||
const _id = await parseLinkId(linkProviders, id, _class as Ref<Class<Doc>>)
|
||||
return await titleProvider(client, _id)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
|
@ -13,15 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import {
|
||||
ActivityInboxNotification,
|
||||
decodeObjectURI,
|
||||
DocNotifyContext,
|
||||
InboxNotification,
|
||||
notificationId
|
||||
} from '@hcengineering/notification'
|
||||
import { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import view from '@hcengineering/view'
|
||||
import view, { decodeObjectURI } from '@hcengineering/view'
|
||||
import {
|
||||
AnyComponent,
|
||||
Component,
|
||||
@ -36,16 +30,16 @@
|
||||
TabList,
|
||||
deviceOptionsStore as deviceInfo
|
||||
} from '@hcengineering/ui'
|
||||
import chunter, { ThreadMessage } from '@hcengineering/chunter'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import { isActivityMessageClass, isReactionMessage } from '@hcengineering/activity-resources'
|
||||
import { get } from 'svelte/store'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { getCurrentAccount, groupByArray, IdMap, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { parseLinkId } from '@hcengineering/view-resources'
|
||||
|
||||
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
|
||||
import SettingsButton from './SettingsButton.svelte'
|
||||
import { getDisplayInboxData, isMentionNotification, openInboxDoc, resolveLocation } from '../../utils'
|
||||
import { getDisplayInboxData, resetInboxContext, resolveLocation, selectInboxContext } from '../../utils'
|
||||
import { InboxData, InboxNotificationsFilter } from '../../types'
|
||||
import InboxGroupedListView from './InboxGroupedListView.svelte'
|
||||
import notification from '../../plugin'
|
||||
@ -69,6 +63,8 @@
|
||||
labelIntl: notification.string.All
|
||||
}
|
||||
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
let showArchive = false
|
||||
let archivedActivityNotifications: InboxNotification[] = []
|
||||
let archivedOtherNotifications: InboxNotification[] = []
|
||||
@ -156,8 +152,9 @@
|
||||
return
|
||||
}
|
||||
|
||||
const [_id] = decodeObjectURI(loc?.loc.path[3] ?? '')
|
||||
const context = $contextByDocStore.get(_id)
|
||||
const [id, _class] = decodeObjectURI(loc?.loc.path[3] ?? '')
|
||||
const _id = await parseLinkId(linkProviders, id, _class)
|
||||
const context = _id ? $contextByDocStore.get(_id) : undefined
|
||||
|
||||
selectedContextId = context?._id
|
||||
|
||||
@ -229,56 +226,13 @@
|
||||
selectedContextId = selectedContext?._id
|
||||
|
||||
if (selectedContext === undefined) {
|
||||
openInboxDoc()
|
||||
resetInboxContext()
|
||||
return
|
||||
}
|
||||
|
||||
const selectedNotification: InboxNotification | undefined = event?.detail?.notification
|
||||
|
||||
if (isMentionNotification(selectedNotification) && isActivityMessageClass(selectedNotification.mentionedInClass)) {
|
||||
const selectedMsg = selectedNotification.mentionedIn as Ref<ActivityMessage>
|
||||
|
||||
openInboxDoc(
|
||||
selectedContext.attachedTo,
|
||||
selectedContext.attachedToClass,
|
||||
isActivityMessageClass(selectedContext.attachedToClass)
|
||||
? (selectedContext.attachedTo as Ref<ActivityMessage>)
|
||||
: undefined,
|
||||
selectedMsg
|
||||
)
|
||||
} else if (hierarchy.isDerived(selectedContext.attachedToClass, activity.class.ActivityMessage)) {
|
||||
const message = event?.detail?.notification?.$lookup?.attachedTo
|
||||
|
||||
if (selectedContext.attachedToClass === chunter.class.ThreadMessage) {
|
||||
const thread = await client.findOne(chunter.class.ThreadMessage, {
|
||||
_id: selectedContext.attachedTo as Ref<ThreadMessage>
|
||||
})
|
||||
openInboxDoc(selectedContext.attachedTo, selectedContext.attachedToClass, thread?.attachedTo, thread?._id)
|
||||
} else if (isReactionMessage(message)) {
|
||||
openInboxDoc(
|
||||
selectedContext.attachedTo,
|
||||
selectedContext.attachedToClass,
|
||||
undefined,
|
||||
selectedContext.attachedTo as Ref<ActivityMessage>
|
||||
)
|
||||
} else {
|
||||
const selectedMsg = (selectedNotification as ActivityInboxNotification)?.attachedTo
|
||||
|
||||
openInboxDoc(
|
||||
selectedContext.attachedTo,
|
||||
selectedContext.attachedToClass,
|
||||
selectedMsg ? (selectedContext.attachedTo as Ref<ActivityMessage>) : undefined,
|
||||
selectedMsg ?? (selectedContext.attachedTo as Ref<ActivityMessage>)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
openInboxDoc(
|
||||
selectedContext.attachedTo,
|
||||
selectedContext.attachedToClass,
|
||||
undefined,
|
||||
(selectedNotification as ActivityInboxNotification)?.attachedTo
|
||||
)
|
||||
}
|
||||
void selectInboxContext(linkProviders, selectedContext, selectedNotification)
|
||||
}
|
||||
|
||||
async function updateSelectedPanel (selectedContext?: DocNotifyContext): Promise<void> {
|
||||
|
@ -18,7 +18,13 @@ import activity, {
|
||||
type DisplayDocUpdateMessage,
|
||||
type DocUpdateMessage
|
||||
} from '@hcengineering/activity'
|
||||
import { activityMessagesComparator, combineActivityMessages, messageInFocus } from '@hcengineering/activity-resources'
|
||||
import {
|
||||
activityMessagesComparator,
|
||||
combineActivityMessages,
|
||||
isActivityMessageClass,
|
||||
isReactionMessage,
|
||||
messageInFocus
|
||||
} from '@hcengineering/activity-resources'
|
||||
import {
|
||||
SortingOrder,
|
||||
getCurrentAccount,
|
||||
@ -31,8 +37,6 @@ import {
|
||||
} from '@hcengineering/core'
|
||||
import notification, {
|
||||
NotificationStatus,
|
||||
decodeObjectURI,
|
||||
encodeObjectURI,
|
||||
notificationId,
|
||||
type ActivityInboxNotification,
|
||||
type Collaborators,
|
||||
@ -57,6 +61,9 @@ import { get, writable } from 'svelte/store'
|
||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||
import { type InboxData, type InboxNotificationsFilter } from './types'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { getObjectLinkId } from '@hcengineering/view-resources'
|
||||
import { decodeObjectURI, encodeObjectURI, type LinkIdProvider } from '@hcengineering/view'
|
||||
import chunter, { type ThreadMessage } from '@hcengineering/chunter'
|
||||
|
||||
export async function hasDocNotifyContextPinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
||||
if (docNotifyContext.hidden) {
|
||||
@ -470,7 +477,7 @@ export async function resolveLocation (loc: Location): Promise<ResolvedLocation
|
||||
|
||||
async function generateLocation (
|
||||
loc: Location,
|
||||
_id: Ref<Doc>,
|
||||
_id: string,
|
||||
_class: Ref<Class<Doc>>
|
||||
): Promise<ResolvedLocation | undefined> {
|
||||
const client = getClient()
|
||||
@ -511,12 +518,13 @@ async function generateLocation (
|
||||
}
|
||||
}
|
||||
|
||||
export function openInboxDoc (
|
||||
async function navigateToInboxDoc (
|
||||
providers: LinkIdProvider[],
|
||||
_id?: Ref<Doc>,
|
||||
_class?: Ref<Class<Doc>>,
|
||||
thread?: Ref<ActivityMessage>,
|
||||
message?: Ref<ActivityMessage>
|
||||
): void {
|
||||
): Promise<void> {
|
||||
const loc = getLocation()
|
||||
|
||||
if (loc.path[2] !== notificationId) {
|
||||
@ -524,14 +532,13 @@ export function openInboxDoc (
|
||||
}
|
||||
|
||||
if (_id === undefined || _class === undefined) {
|
||||
loc.query = { message: null }
|
||||
loc.path.length = 3
|
||||
localStorage.setItem(`${locationStorageKeyId}_${notificationId}`, JSON.stringify(loc))
|
||||
navigate(loc)
|
||||
resetInboxContext()
|
||||
return
|
||||
}
|
||||
|
||||
loc.path[3] = encodeObjectURI(_id, _class)
|
||||
const id = await getObjectLinkId(providers, _id, _class)
|
||||
|
||||
loc.path[3] = encodeObjectURI(id, _class)
|
||||
|
||||
if (thread !== undefined) {
|
||||
loc.path[4] = thread
|
||||
@ -546,6 +553,96 @@ export function openInboxDoc (
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export function resetInboxContext (): void {
|
||||
const loc = getLocation()
|
||||
|
||||
if (loc.path[2] !== notificationId) {
|
||||
return
|
||||
}
|
||||
|
||||
loc.query = { message: null }
|
||||
loc.path.length = 3
|
||||
|
||||
localStorage.setItem(`${locationStorageKeyId}_${notificationId}`, JSON.stringify(loc))
|
||||
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export async function selectInboxContext (
|
||||
linkProviders: LinkIdProvider[],
|
||||
context: DocNotifyContext,
|
||||
notification?: WithLookup<InboxNotification>
|
||||
): Promise<void> {
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
if (isMentionNotification(notification) && isActivityMessageClass(notification.mentionedInClass)) {
|
||||
const selectedMsg = notification.mentionedIn as Ref<ActivityMessage>
|
||||
|
||||
void navigateToInboxDoc(
|
||||
linkProviders,
|
||||
context.attachedTo,
|
||||
context.attachedToClass,
|
||||
isActivityMessageClass(context.attachedToClass) ? (context.attachedTo as Ref<ActivityMessage>) : undefined,
|
||||
selectedMsg
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
if (hierarchy.isDerived(context.attachedToClass, activity.class.ActivityMessage)) {
|
||||
const message = (notification as WithLookup<ActivityInboxNotification>)?.$lookup?.attachedTo
|
||||
|
||||
if (context.attachedToClass === chunter.class.ThreadMessage) {
|
||||
const thread = await client.findOne(
|
||||
chunter.class.ThreadMessage,
|
||||
{
|
||||
_id: context.attachedTo as Ref<ThreadMessage>
|
||||
},
|
||||
{ projection: { _id: 1, attachedTo: 1 } }
|
||||
)
|
||||
|
||||
void navigateToInboxDoc(
|
||||
linkProviders,
|
||||
context.attachedTo,
|
||||
context.attachedToClass,
|
||||
thread?.attachedTo,
|
||||
thread?._id
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (isReactionMessage(message)) {
|
||||
void navigateToInboxDoc(
|
||||
linkProviders,
|
||||
context.attachedTo,
|
||||
context.attachedToClass,
|
||||
undefined,
|
||||
context.attachedTo as Ref<ActivityMessage>
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const selectedMsg = (notification as ActivityInboxNotification)?.attachedTo
|
||||
|
||||
void navigateToInboxDoc(
|
||||
linkProviders,
|
||||
context.attachedTo,
|
||||
context.attachedToClass,
|
||||
selectedMsg !== undefined ? (context.attachedTo as Ref<ActivityMessage>) : undefined,
|
||||
selectedMsg ?? (context.attachedTo as Ref<ActivityMessage>)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
void navigateToInboxDoc(
|
||||
linkProviders,
|
||||
context.attachedTo,
|
||||
context.attachedToClass,
|
||||
undefined,
|
||||
(notification as ActivityInboxNotification)?.attachedTo
|
||||
)
|
||||
}
|
||||
|
||||
export const pushAllowed = writable<boolean>(false)
|
||||
|
||||
export async function checkPermission (value: boolean): Promise<boolean> {
|
||||
|
@ -420,5 +420,4 @@ const notification = plugin(notificationId, {
|
||||
}
|
||||
})
|
||||
|
||||
export * from './utils'
|
||||
export default notification
|
||||
|
@ -76,6 +76,7 @@ import {
|
||||
getTalentId,
|
||||
getVacTitle,
|
||||
objectLinkProvider,
|
||||
parseLinkId,
|
||||
resolveLocation
|
||||
} from './utils'
|
||||
|
||||
@ -411,7 +412,8 @@ export default async (): Promise<Resources> => ({
|
||||
GetIdObjectLinkFragment: getObjectLink,
|
||||
HideDoneState: hideDoneState,
|
||||
HideArchivedVacancies: hideArchivedVacancies,
|
||||
ApplicantHasEmail: applicantHasEmail
|
||||
ApplicantHasEmail: applicantHasEmail,
|
||||
ParseLinkId: parseLinkId
|
||||
},
|
||||
resolver: {
|
||||
Location: resolveLocation
|
||||
|
@ -149,7 +149,7 @@ export default mergeIds(recruitId, recruit, {
|
||||
CreateCandidate: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
IdProvider: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||
IdProvider: '' as Resource<(doc: Doc) => Promise<string>>,
|
||||
AppTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
AppIdentifierProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
VacTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
|
@ -92,15 +92,41 @@ async function generateIdLocation (loc: Location, shortLink: string): Promise<Re
|
||||
}
|
||||
}
|
||||
|
||||
async function generateLocation (loc: Location, shortLink: string): Promise<ResolvedLocation | undefined> {
|
||||
export async function parseLinkId (id: string): Promise<Ref<Doc> | undefined> {
|
||||
if (isShortId(id)) {
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const data = getShortLinkData(hierarchy, id)
|
||||
|
||||
if (data === undefined) {
|
||||
return id as Ref<Doc>
|
||||
}
|
||||
|
||||
const [_class, , number] = data
|
||||
|
||||
if (_class === undefined) {
|
||||
return id as Ref<Doc>
|
||||
}
|
||||
|
||||
const doc = await client.findOne(_class, { number }, { projection: { _id: 1 } })
|
||||
|
||||
return doc?._id
|
||||
}
|
||||
|
||||
return id as Ref<Doc>
|
||||
}
|
||||
|
||||
function getShortLinkData (
|
||||
hierarchy: Hierarchy,
|
||||
shortLink: string
|
||||
): [Ref<Class<Doc>> | undefined, string, number] | undefined {
|
||||
const tokens = shortLink.split('-')
|
||||
if (tokens.length < 2) {
|
||||
return undefined
|
||||
}
|
||||
const classLabel = tokens[0]
|
||||
const number = Number(tokens[1])
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const classes = [recruit.class.Applicant, recruit.class.Vacancy, recruit.class.Review]
|
||||
let _class: Ref<Class<Doc>> | undefined
|
||||
for (const clazz of classes) {
|
||||
@ -109,6 +135,21 @@ async function generateLocation (loc: Location, shortLink: string): Promise<Reso
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return [_class, classLabel, number]
|
||||
}
|
||||
|
||||
async function generateLocation (loc: Location, shortLink: string): Promise<ResolvedLocation | undefined> {
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const data = getShortLinkData(hierarchy, shortLink)
|
||||
|
||||
if (data === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const [_class, classLabel, number] = data
|
||||
|
||||
if (_class === undefined) {
|
||||
console.error(`Not found class with short label ${classLabel}`)
|
||||
return undefined
|
||||
|
@ -41,7 +41,6 @@
|
||||
import ToDoGroup from './ToDoGroup.svelte'
|
||||
import MenuClose from './icons/MenuClose.svelte'
|
||||
import MenuOpen from './icons/MenuOpen.svelte'
|
||||
import IconDiff from './icons/Diff.svelte'
|
||||
import time from '../plugin'
|
||||
|
||||
export let mode: ToDosMode
|
||||
|
@ -20,6 +20,7 @@
|
||||
import { Component, showPanel } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import time from '../plugin'
|
||||
import { getObjectLinkId } from '@hcengineering/view-resources'
|
||||
|
||||
export let todo: ToDo
|
||||
export let kind: 'default' | 'todo-line' = 'default'
|
||||
@ -27,6 +28,9 @@
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
$: presenter = hierarchy.classHierarchyMixin<Doc, ItemPresenter>(todo.attachedToClass, time.mixin.ItemPresenter)
|
||||
|
||||
let doc: Doc | undefined = undefined
|
||||
@ -35,12 +39,14 @@
|
||||
doc = res[0]
|
||||
})
|
||||
|
||||
async function click (event: MouseEvent) {
|
||||
async function click (event: MouseEvent): Promise<void> {
|
||||
event.stopPropagation()
|
||||
if (!doc) return
|
||||
const panelComponent = hierarchy.classHierarchyMixin<Class<Doc>, ObjectPanel>(doc._class, view.mixin.ObjectPanel)
|
||||
const component = panelComponent?.component ?? view.component.EditDoc
|
||||
showPanel(component, doc._id, doc._class, 'content')
|
||||
const id = await getObjectLinkId(linkProviders, doc._id, doc._class, doc)
|
||||
|
||||
showPanel(component, id, doc._class, 'content')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -15,9 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||
import { Class, Doc, Ref, WithLookup } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import presentation, {
|
||||
ActionContext,
|
||||
ComponentExtensions,
|
||||
@ -44,21 +42,23 @@
|
||||
import view from '@hcengineering/view'
|
||||
import { DocNavLink, ParentsNavigator, showMenu } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { generateIssueShortLink } from '../../../issues'
|
||||
import { generateIssueShortLink, getIssueIdByIdentifier } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
import IssueStatusActivity from '../IssueStatusActivity.svelte'
|
||||
import ControlPanel from './ControlPanel.svelte'
|
||||
import CopyToClipboard from './CopyToClipboard.svelte'
|
||||
import SubIssueSelector from './SubIssueSelector.svelte'
|
||||
import SubIssues from './SubIssues.svelte'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
|
||||
export let _id: Ref<Issue>
|
||||
export let _id: Ref<Issue> | string
|
||||
export let _class: Ref<Class<Issue>>
|
||||
export let embedded: boolean = false
|
||||
export let kind: 'default' | 'modern' = 'default'
|
||||
export let readonly: boolean = false
|
||||
|
||||
let lastId: Ref<Doc> = _id
|
||||
let lastId: Ref<Issue> | undefined
|
||||
|
||||
const queryClient = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@ -70,28 +70,39 @@
|
||||
let descriptionBox: AttachmentStyleBoxCollabEditor
|
||||
let showAllMixins: boolean
|
||||
|
||||
const inboxClient = getResource(notification.function.GetInboxNotificationsClient).then((res) => res())
|
||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||
|
||||
$: read(_id)
|
||||
function read (_id: Ref<Doc>): void {
|
||||
if (lastId !== _id) {
|
||||
let issueId: Ref<Issue> | undefined
|
||||
|
||||
$: void getIssueIdByIdentifier(_id).then((res) => {
|
||||
issueId = res ?? (_id as Ref<Issue>)
|
||||
|
||||
if (lastId === undefined) {
|
||||
lastId = issueId
|
||||
}
|
||||
})
|
||||
|
||||
$: read(issueId)
|
||||
|
||||
function read (_id?: Ref<Issue>): void {
|
||||
if (_id && lastId && lastId !== _id) {
|
||||
const prev = lastId
|
||||
lastId = _id
|
||||
void inboxClient.then((client) => client.readDoc(getClient(), prev))
|
||||
void inboxClient.readDoc(getClient(), prev)
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(async () => {
|
||||
void inboxClient.then((client) => client.readDoc(getClient(), _id))
|
||||
if (issueId === undefined) return
|
||||
void inboxClient.readDoc(getClient(), issueId)
|
||||
})
|
||||
|
||||
$: _id !== undefined &&
|
||||
_class !== undefined &&
|
||||
$: if (issueId !== undefined && _class !== undefined) {
|
||||
queryClient.query<Issue>(
|
||||
_class,
|
||||
{ _id },
|
||||
{ _id: issueId },
|
||||
async (result) => {
|
||||
if (lastId !== _id) {
|
||||
if (lastId !== issueId) {
|
||||
await save()
|
||||
}
|
||||
;[issue] = result
|
||||
@ -103,6 +114,7 @@
|
||||
limit: 1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$: canSave = title.trim().length > 0
|
||||
$: hasParentIssue = issue?.attachedTo !== tracker.ids.NoParent
|
||||
|
@ -89,6 +89,7 @@ import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svel
|
||||
import IssueExtra from './components/issues/IssueExtra.svelte'
|
||||
import IssueStatusPresenter from './components/issues/IssueStatusPresenter.svelte'
|
||||
import {
|
||||
getIssueIdByIdentifier,
|
||||
getIssueTitle,
|
||||
getTitle,
|
||||
issueIdentifierProvider,
|
||||
@ -689,6 +690,7 @@ export default async (): Promise<Resources> => ({
|
||||
ComponentTitleProvider: getComponentTitle,
|
||||
MilestoneTitleProvider: getMilestoneTitle,
|
||||
GetIssueId: getTitle,
|
||||
GetIssueIdByIdentifier: getIssueIdByIdentifier,
|
||||
GetIssueLink: issueLinkProvider,
|
||||
GetIssueLinkFragment: issueLinkFragmentProvider,
|
||||
GetIssueTitle: getIssueTitle,
|
||||
|
@ -41,7 +41,7 @@ export async function getTitle (doc: Doc): Promise<string> {
|
||||
}
|
||||
|
||||
export function generateIssuePanelUri (issue: Issue): string {
|
||||
return getPanelURI(tracker.component.EditIssue, issue._id, issue._class, 'content')
|
||||
return getPanelURI(tracker.component.EditIssue, issue.identifier, issue._class, 'content')
|
||||
}
|
||||
|
||||
export async function issueLinkFragmentProvider (doc: Doc): Promise<Location> {
|
||||
@ -126,3 +126,10 @@ export async function updateIssueRelation (
|
||||
}
|
||||
await client.update(value, update)
|
||||
}
|
||||
|
||||
export async function getIssueIdByIdentifier (identifier: string): Promise<Ref<Issue> | undefined> {
|
||||
const client = getClient()
|
||||
const issue = await client.findOne(tracker.class.Issue, { identifier }, { projection: { _id: 1 } })
|
||||
|
||||
return issue?._id
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import { type StatusCategory, type Client, type Doc, type Ref, type Space } from
|
||||
import type { Asset, IntlString, Metadata, Resource } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import tracker, { trackerId, type IssueDraft } from '@hcengineering/tracker'
|
||||
import tracker, { trackerId, type IssueDraft, type Issue } from '@hcengineering/tracker'
|
||||
import { type AnyComponent, type Location } from '@hcengineering/ui'
|
||||
import {
|
||||
type CreateAggregationManagerFunc,
|
||||
@ -378,7 +378,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
IssueIdentifierProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
ComponentTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
MilestoneTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
|
||||
GetIssueId: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||
GetIssueId: '' as Resource<(doc: Doc) => Promise<string>>,
|
||||
GetIssueLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||
GetIssueLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
|
||||
GetIssueTitle: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||
@ -393,7 +393,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
GetVisibleFilters: '' as Resource<(filters: KeyFilter[], space?: Ref<Space>) => Promise<KeyFilter[]>>,
|
||||
IsProjectJoined: '' as Resource<(space: Space) => Promise<boolean>>,
|
||||
IssueChatTitleProvider: '' as Resource<(object: Doc) => string>,
|
||||
GetIssueStatusCategories: '' as Resource<(project: ProjectType) => Array<Ref<StatusCategory>>>
|
||||
GetIssueStatusCategories: '' as Resource<(project: ProjectType) => Array<Ref<StatusCategory>>>,
|
||||
GetIssueIdByIdentifier: '' as Resource<(id: string) => Promise<Ref<Issue> | undefined>>
|
||||
},
|
||||
aggregation: {
|
||||
CreateComponentAggregationManager: '' as CreateAggregationManagerFunc,
|
||||
|
@ -31,26 +31,37 @@
|
||||
import view, { AttributeCategory } from '@hcengineering/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
|
||||
import { DocNavLink, ParentsNavigator, getDocAttrsInfo, getDocLabel, getDocMixins, showMenu } from '..'
|
||||
import { DocNavLink, ParentsNavigator, getDocAttrsInfo, getDocLabel, getDocMixins, showMenu, parseLinkId } from '..'
|
||||
import { getCollectionCounter } from '../utils'
|
||||
import DocAttributeBar from './DocAttributeBar.svelte'
|
||||
|
||||
export let _id: Ref<Doc>
|
||||
export let _id: Ref<Doc> | string
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let embedded: boolean = false
|
||||
export let readonly: boolean = false
|
||||
|
||||
let realObjectClass: Ref<Class<Doc>> = _class
|
||||
let lastId: Ref<Doc> = _id
|
||||
let lastId: Ref<Doc> | undefined
|
||||
let objectId: Ref<Doc> | undefined
|
||||
let object: Doc
|
||||
|
||||
const pClient = getClient()
|
||||
const hierarchy = pClient.getHierarchy()
|
||||
const inboxClient = getResource(notification.function.GetInboxNotificationsClient).then((res) => res())
|
||||
const linkProviders = pClient.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
$: read(_id)
|
||||
function read (_id: Ref<Doc>): void {
|
||||
if (lastId !== _id) {
|
||||
$: void parseLinkId(linkProviders, _id, _class).then((res) => {
|
||||
objectId = res ?? (_id as Ref<Doc>)
|
||||
if (lastId !== undefined) {
|
||||
return
|
||||
}
|
||||
lastId = objectId
|
||||
})
|
||||
|
||||
$: read(objectId)
|
||||
|
||||
function read (_id?: Ref<Doc>): void {
|
||||
if (objectId && lastId && lastId !== _id) {
|
||||
const prev = lastId
|
||||
lastId = _id
|
||||
void inboxClient.then(async (client) => {
|
||||
@ -61,14 +72,15 @@
|
||||
|
||||
onDestroy(async () => {
|
||||
await inboxClient.then(async (client) => {
|
||||
await client.readDoc(pClient, _id)
|
||||
if (objectId === undefined) return
|
||||
await client.readDoc(pClient, objectId)
|
||||
})
|
||||
})
|
||||
|
||||
const query = createQuery()
|
||||
$: updateQuery(_id, _class)
|
||||
$: updateQuery(objectId, _class)
|
||||
|
||||
function updateQuery (_id: Ref<Doc>, _class: Ref<Class<Doc>>): void {
|
||||
function updateQuery (_id?: Ref<Doc>, _class?: Ref<Class<Doc>>): void {
|
||||
if (_id && _class) {
|
||||
query.query(_class, { _id }, (result) => {
|
||||
object = result[0]
|
||||
@ -146,12 +158,13 @@
|
||||
|
||||
$: editorFooter = getEditorFooter(_class, object)
|
||||
|
||||
const getEditorOrDefault = reduceCalls(async function (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<void> {
|
||||
const getEditorOrDefault = reduceCalls(async function (_class: Ref<Class<Doc>>, _id?: Ref<Doc>): Promise<void> {
|
||||
if (objectId === undefined) return
|
||||
await updateKeys()
|
||||
mainEditor = getEditor(_class)
|
||||
})
|
||||
|
||||
$: void getEditorOrDefault(realObjectClass, _id)
|
||||
$: void getEditorOrDefault(realObjectClass, objectId)
|
||||
|
||||
let title: string | undefined = undefined
|
||||
let rawTitle: string = ''
|
||||
|
@ -87,7 +87,8 @@ import view, {
|
||||
type BuildModelOptions,
|
||||
type CollectionPresenter,
|
||||
type Viewlet,
|
||||
type ViewletDescriptor
|
||||
type ViewletDescriptor,
|
||||
type LinkIdProvider
|
||||
} from '@hcengineering/view'
|
||||
|
||||
import contact, { getName, type Contact, type PersonAccount } from '@hcengineering/contact'
|
||||
@ -1023,8 +1024,16 @@ export async function getObjectLinkFragment (
|
||||
}
|
||||
}
|
||||
const loc = getCurrentResolvedLocation()
|
||||
const idProvider = hierarchy.classHierarchyMixin(Hierarchy.mixinOrClass(object), view.mixin.LinkIdProvider)
|
||||
|
||||
let id: string = object._id
|
||||
if (idProvider !== undefined) {
|
||||
const encodeFn = await getResource(idProvider.encode)
|
||||
id = await encodeFn(object)
|
||||
}
|
||||
|
||||
if (hasResource(component) === true) {
|
||||
loc.fragment = getPanelURI(component, object._id, Hierarchy.mixinOrClass(object), 'content')
|
||||
loc.fragment = getPanelURI(component, id, Hierarchy.mixinOrClass(object), 'content')
|
||||
}
|
||||
return loc
|
||||
}
|
||||
@ -1448,3 +1457,43 @@ export function getCollaborationUser (): CollaborationUser {
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
export async function getObjectLinkId (
|
||||
providers: LinkIdProvider[],
|
||||
_id: Ref<Doc>,
|
||||
_class: Ref<Class<Doc>>,
|
||||
doc?: Doc
|
||||
): Promise<string> {
|
||||
const provider = providers.find(({ _id }) => _id === _class)
|
||||
|
||||
if (provider === undefined) {
|
||||
return _id
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const object = doc ?? (await client.findOne(_class, { _id }))
|
||||
|
||||
if (object === undefined) {
|
||||
return _id
|
||||
}
|
||||
|
||||
const encodeFn = await getResource(provider.encode)
|
||||
return await encodeFn(object)
|
||||
}
|
||||
|
||||
export async function parseLinkId<T extends Doc> (
|
||||
providers: LinkIdProvider[],
|
||||
id: string,
|
||||
_class: Ref<Class<T>>
|
||||
): Promise<Ref<T>> {
|
||||
const provider = providers.find(({ _id }) => _id === _class)
|
||||
|
||||
if (provider === undefined) {
|
||||
return id as Ref<T>
|
||||
}
|
||||
|
||||
const decodeFn = await getResource(provider.decode)
|
||||
const _id = await decodeFn(id)
|
||||
|
||||
return (_id ?? id) as Ref<T>
|
||||
}
|
||||
|
@ -60,10 +60,12 @@ import {
|
||||
ViewAction,
|
||||
Viewlet,
|
||||
ViewletDescriptor,
|
||||
ViewletPreference
|
||||
ViewletPreference,
|
||||
LinkIdProvider
|
||||
} from './types'
|
||||
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -103,6 +105,7 @@ const view = plugin(viewId, {
|
||||
AllValuesFunc: '' as Ref<Mixin<AllValuesFunc>>,
|
||||
ObjectPanel: '' as Ref<Mixin<ObjectPanel>>,
|
||||
LinkProvider: '' as Ref<Mixin<LinkProvider>>,
|
||||
LinkIdProvider: '' as Ref<Mixin<LinkIdProvider>>,
|
||||
SpacePresenter: '' as Ref<Mixin<SpacePresenter>>,
|
||||
AttributeFilterPresenter: '' as Ref<Mixin<AttributeFilterPresenter>>,
|
||||
Aggregation: '' as Ref<Mixin<Aggregation>>,
|
||||
|
@ -756,6 +756,11 @@ export interface LinkProvider extends Class<Doc> {
|
||||
encode: Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
|
||||
}
|
||||
|
||||
export interface LinkIdProvider extends Class<Doc> {
|
||||
encode: Resource<(doc: Doc) => Promise<string>>
|
||||
decode: Resource<(id: string) => Promise<Ref<Doc> | undefined>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Ref, Doc, Class } from '@hcengineering/core'
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
|
||||
export function decodeObjectURI (value: string): [Ref<Doc>, Ref<Class<Doc>>] {
|
||||
return decodeURIComponent(value).split('|') as [Ref<Doc>, Ref<Class<Doc>>]
|
||||
}
|
||||
|
||||
export function encodeObjectURI (_id: Ref<Doc>, _class: Ref<Class<Doc>>): string {
|
||||
export function encodeObjectURI (_id: string, _class: Ref<Class<Doc>>): string {
|
||||
return [_id, _class].join('|')
|
||||
}
|
@ -67,7 +67,8 @@
|
||||
NavLink,
|
||||
accessDeniedStore,
|
||||
migrateViewOpttions,
|
||||
updateFocus
|
||||
updateFocus,
|
||||
parseLinkId
|
||||
} from '@hcengineering/view-resources'
|
||||
import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@hcengineering/workbench'
|
||||
import { getContext, onDestroy, onMount, tick } from 'svelte'
|
||||
@ -117,7 +118,10 @@
|
||||
let panelInstance: PanelInstance
|
||||
let popupInstance: Popup
|
||||
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
$deviceInfo.navigator.visible = getMetadata(workbench.metadata.NavigationExpandedDefault) ?? true
|
||||
|
||||
async function toggleNav (): Promise<void> {
|
||||
$deviceInfo.navigator.visible = !$deviceInfo.navigator.visible
|
||||
closeTooltip()
|
||||
@ -202,14 +206,15 @@
|
||||
async function getWindowTitle (loc: Location): Promise<string | undefined> {
|
||||
if (loc.fragment == null) return
|
||||
const hierarchy = client.getHierarchy()
|
||||
const [, _id, _class] = decodeURIComponent(loc.fragment).split('|')
|
||||
const [, id, _class] = decodeURIComponent(loc.fragment).split('|')
|
||||
if (_class == null) return
|
||||
|
||||
const mixin = hierarchy.classHierarchyMixin(_class as Ref<Class<Doc>>, view.mixin.ObjectTitle)
|
||||
if (mixin === undefined) return
|
||||
const titleProvider = await getResource(mixin.titleProvider)
|
||||
try {
|
||||
return await titleProvider(client, _id as Ref<Doc>)
|
||||
const _id = await parseLinkId(linkProviders, id, _class as Ref<Class<Doc>>)
|
||||
return await titleProvider(client, _id)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
@ -398,7 +403,9 @@
|
||||
const props = decodeURIComponent(fragment).split('|')
|
||||
|
||||
if (props.length >= 3) {
|
||||
const doc = await client.findOne<Doc>(props[2] as Ref<Class<Doc>>, { _id: props[1] as Ref<Doc> })
|
||||
const _class = props[2] as Ref<Class<Doc>>
|
||||
const _id = await parseLinkId(linkProviders, props[1], _class)
|
||||
const doc = await client.findOne<Doc>(_class, { _id })
|
||||
|
||||
if (doc !== undefined) {
|
||||
const provider = ListSelectionProvider.Find(doc._id)
|
||||
@ -408,8 +415,8 @@
|
||||
})
|
||||
openPanel(
|
||||
props[0] as AnyComponent,
|
||||
props[1],
|
||||
props[2],
|
||||
_id,
|
||||
_class,
|
||||
(props[3] ?? undefined) as PopupAlignment,
|
||||
(props[4] ?? undefined) as AnyComponent
|
||||
)
|
||||
|
@ -26,6 +26,10 @@ export async function documentHTMLPresenter (doc: Doc, control: TriggerControl):
|
||||
return `<a href="${link}">${document.name}</a>`
|
||||
}
|
||||
|
||||
export async function documentLinkIdProvider (doc: Document): Promise<string> {
|
||||
return getDocumentId(doc)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -38,6 +42,7 @@ export async function documentTextPresenter (doc: Doc): Promise<string> {
|
||||
export default async () => ({
|
||||
function: {
|
||||
DocumentHTMLPresenter: documentHTMLPresenter,
|
||||
DocumentTextPresenter: documentTextPresenter
|
||||
DocumentTextPresenter: documentTextPresenter,
|
||||
DocumentLinkIdProvider: documentLinkIdProvider
|
||||
}
|
||||
})
|
||||
|
@ -39,6 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.1"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
//
|
||||
//
|
||||
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { Presenter } from '@hcengineering/server-notification'
|
||||
@ -18,6 +19,7 @@ export const serverDocumentId = 'server-document' as Plugin
|
||||
export default plugin(serverDocumentId, {
|
||||
function: {
|
||||
DocumentHTMLPresenter: '' as Resource<Presenter>,
|
||||
DocumentTextPresenter: '' as Resource<Presenter>
|
||||
DocumentTextPresenter: '' as Resource<Presenter>,
|
||||
DocumentLinkIdProvider: '' as Resource<(doc: Doc) => Promise<string>>
|
||||
}
|
||||
})
|
||||
|
@ -49,6 +49,7 @@
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/text": "^0.6.5",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/server-view": "^0.6.0",
|
||||
"web-push": "~3.6.7"
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ import notification, {
|
||||
Collaborators,
|
||||
CommonInboxNotification,
|
||||
DocNotifyContext,
|
||||
encodeObjectURI,
|
||||
InboxNotification,
|
||||
MentionInboxNotification,
|
||||
notificationId,
|
||||
@ -80,6 +79,9 @@ import serverNotification, {
|
||||
import { stripTags } from '@hcengineering/text'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import webpush, { WebPushError } from 'web-push'
|
||||
import { encodeObjectURI } from '@hcengineering/view'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
|
||||
import { Content, NotifyResult, NotifyParams } from './types'
|
||||
import {
|
||||
getHTMLPresenter,
|
||||
@ -554,12 +556,24 @@ export async function createPushFromInbox (
|
||||
cache.set(senderPerson._id, senderPerson)
|
||||
}
|
||||
|
||||
const path = [
|
||||
workbenchId,
|
||||
control.workspace.workspaceUrl,
|
||||
notificationId,
|
||||
encodeObjectURI(attachedTo, attachedToClass)
|
||||
]
|
||||
const linkProviders = control.modelDb.findAllSync(serverView.mixin.ServerLinkIdProvider, {})
|
||||
const provider = linkProviders.find(({ _id }) => _id === attachedToClass)
|
||||
|
||||
let id: string = attachedTo
|
||||
|
||||
if (provider !== undefined) {
|
||||
const encodeFn = await getResource(provider.encode)
|
||||
const doc = cache.get(attachedTo) ?? (await control.findAll(attachedToClass, { _id: attachedTo }))[0]
|
||||
|
||||
if (doc === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
cache.set(doc._id, doc)
|
||||
id = await encodeFn(doc, control)
|
||||
}
|
||||
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, notificationId, encodeObjectURI(id, attachedToClass)]
|
||||
await createPushNotification(control, targetUser, title, body, _id, senderPerson, path)
|
||||
return control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, notification.space.Notifications, {
|
||||
user: targetUser,
|
||||
|
@ -110,7 +110,8 @@ export default async () => ({
|
||||
VacancyHTMLPresenter: vacancyHTMLPresenter,
|
||||
VacancyTextPresenter: vacancyTextPresenter,
|
||||
ApplicationHTMLPresenter: applicationHTMLPresenter,
|
||||
ApplicationTextPresenter: applicationTextPresenter
|
||||
ApplicationTextPresenter: applicationTextPresenter,
|
||||
LinkIdProvider: getSequenceId
|
||||
},
|
||||
trigger: {
|
||||
OnRecruitUpdate
|
||||
|
@ -40,6 +40,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/core": "^0.6.32"
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
@ -31,7 +32,8 @@ export default plugin(serverRecruitId, {
|
||||
ApplicationHTMLPresenter: '' as Resource<Presenter>,
|
||||
ApplicationTextPresenter: '' as Resource<Presenter>,
|
||||
VacancyHTMLPresenter: '' as Resource<Presenter>,
|
||||
VacancyTextPresenter: '' as Resource<Presenter>
|
||||
VacancyTextPresenter: '' as Resource<Presenter>,
|
||||
LinkIdProvider: '' as Resource<(doc: Doc) => Promise<string>>
|
||||
},
|
||||
trigger: {
|
||||
OnRecruitUpdate: '' as Resource<TriggerFunc>
|
||||
|
@ -501,12 +501,17 @@ function updateIssueParentEstimations (
|
||||
}
|
||||
}
|
||||
|
||||
async function issueLinkIdProvider (issue: Issue): Promise<string> {
|
||||
return issue.identifier
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
IssueHTMLPresenter: issueHTMLPresenter,
|
||||
IssueTextPresenter: issueTextPresenter,
|
||||
IssueNotificationContentProvider: getIssueNotificationContent
|
||||
IssueNotificationContentProvider: getIssueNotificationContent,
|
||||
IssueLinkIdProvider: issueLinkIdProvider
|
||||
},
|
||||
trigger: {
|
||||
OnIssueUpdate,
|
||||
|
@ -40,6 +40,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/core": "^0.6.32"
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
@ -30,7 +31,8 @@ export default plugin(serverTrackerId, {
|
||||
function: {
|
||||
IssueHTMLPresenter: '' as Resource<Presenter>,
|
||||
IssueTextPresenter: '' as Resource<Presenter>,
|
||||
IssueNotificationContentProvider: '' as Resource<NotificationContentProvider>
|
||||
IssueNotificationContentProvider: '' as Resource<NotificationContentProvider>,
|
||||
IssueLinkIdProvider: '' as Resource<(doc: Doc) => Promise<string>>
|
||||
},
|
||||
trigger: {
|
||||
OnIssueUpdate: '' as Resource<TriggerFunc>,
|
||||
|
@ -40,6 +40,7 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
@ -14,18 +14,26 @@
|
||||
//
|
||||
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
import { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { Class, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const serverViewId = 'server-view' as Plugin
|
||||
|
||||
export interface ServerLinkIdProvider extends Class<Doc> {
|
||||
encode: Resource<(doc: Doc, control: TriggerControl) => Promise<string>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export default plugin(serverViewId, {
|
||||
mixin: {
|
||||
ServerLinkIdProvider: '' as Ref<Mixin<ServerLinkIdProvider>>
|
||||
},
|
||||
trigger: {
|
||||
OnCustomAttributeRemove: '' as Resource<TriggerFunc>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user