UBERF-5594: render mentions before object is loaded (#4738)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-02-21 08:52:19 +04:00 committed by GitHub
parent e454342dac
commit a85db1b78a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 392 additions and 147 deletions

View File

@ -277,6 +277,14 @@ export function createModel (builder: Builder): void {
component: contact.component.CreateOrganization
})
builder.mixin(contact.class.Contact, core.class.Class, view.mixin.ObjectIdentifier, {
provider: contact.function.ContactTitleProvider
})
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectTooltip, {
provider: contact.function.PersonTooltipProvider
})
builder.createDoc(
workbench.class.Application,
core.space.Model,

View File

@ -102,6 +102,10 @@ export function createModel (builder: Builder): void {
presenter: inventory.component.CategoryPresenter
})
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.ObjectIdentifier, {
provider: inventory.function.CategoryIdProvider
})
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.AttributePresenter, {
presenter: inventory.component.CategoryRefPresenter
})
@ -110,6 +114,10 @@ export function createModel (builder: Builder): void {
presenter: inventory.component.ProductPresenter
})
builder.mixin(inventory.class.Product, core.class.Class, view.mixin.ObjectIdentifier, {
provider: inventory.function.ProductIdProvider
})
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.ObjectPresenter, {
presenter: inventory.component.VariantPresenter
})

View File

@ -15,10 +15,10 @@
//
import { type ChatMessageViewlet } from '@hcengineering/chunter'
import type { Ref } from '@hcengineering/core'
import type { Client, Doc, Ref } from '@hcengineering/core'
import { inventoryId } from '@hcengineering/inventory'
import inventory from '@hcengineering/inventory-resources/src/plugin'
import { type IntlString, mergeIds } from '@hcengineering/platform'
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import type { AnyComponent } from '@hcengineering/ui/src/types'
import { type Action, type ActionCategory, type ViewAction, type Viewlet } from '@hcengineering/view'
export default mergeIds(inventoryId, inventory, {
@ -51,5 +51,9 @@ export default mergeIds(inventoryId, inventory, {
ids: {
ProductChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
CategoryChatMessageViewlet: '' as Ref<ChatMessageViewlet>
},
function: {
ProductIdProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
CategoryIdProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
}
})

View File

@ -573,6 +573,10 @@ export function createModel (builder: Builder): void {
titleProvider: lead.function.LeadTitleProvider
})
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.ObjectIdentifier, {
provider: lead.function.LeadIdProvider
})
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.ClassFilters, {
filters: ['attachedTo']
})

View File

@ -1132,6 +1132,10 @@ export function createModel (builder: Builder): void {
titleProvider: recruit.function.VacTitleProvider
})
builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectIdentifier, {
provider: recruit.function.VacTitleProvider
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.LinkProvider, {
encode: recruit.function.GetObjectLinkFragment
})

View File

@ -209,6 +209,10 @@ function defineFilters (builder: Builder): void {
titleProvider: tracker.function.MilestoneTitleProvider
})
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectIdentifier, {
provider: tracker.function.MilestoneTitleProvider
})
//
// Project
//
@ -234,6 +238,10 @@ function defineFilters (builder: Builder): void {
titleProvider: tracker.function.ComponentTitleProvider
})
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ObjectIdentifier, {
provider: tracker.function.ComponentTitleProvider
})
//
// Type Milestone Status
//

View File

@ -30,7 +30,7 @@ import core, { TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
import presentation from '@hcengineering/model-presentation'
import { type Asset, type IntlString, type Resource, type Status } from '@hcengineering/platform'
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
import { type AnyComponent, type LabelAndProps, type Location } from '@hcengineering/ui/src/types'
import {
type Action,
type ActionCategory,
@ -86,7 +86,8 @@ import {
type ViewletDescriptor,
type ViewletPreference,
type ObjectIdentifier,
type ObjectIcon
type ObjectIcon,
type ObjectTooltip
} from '@hcengineering/view'
import view from './plugin'
@ -270,6 +271,11 @@ export class TObjectIdentifier extends TClass implements ObjectIdentifier {
provider!: Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>
}
@Mixin(view.mixin.ObjectTooltip, core.class.Class)
export class TObjectTooltip extends TClass implements ObjectTooltip {
provider!: Resource<(client: Client, doc?: Doc | null) => Promise<LabelAndProps | undefined>>
}
@Mixin(view.mixin.ListHeaderExtra, core.class.Class)
export class TListHeaderExtra extends TClass implements ListHeaderExtra {
presenters!: AnyComponent[]
@ -459,6 +465,7 @@ export function createModel (builder: Builder): void {
TAggregation,
TGroupping,
TObjectIdentifier,
TObjectTooltip,
TObjectIcon
)

View File

@ -14,12 +14,12 @@
// limitations under the License.
-->
<script lang="ts">
import { CheckBox, Component, navigate, parseLocation } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { CheckBox, navigate, parseLocation } from '@hcengineering/ui'
import { Class, Doc, Ref } from '@hcengineering/core'
import { getMetadata } from '@hcengineering/platform'
import presentation from '../../plugin'
import ObjectNode from './ObjectNode.svelte'
export let nodes: NodeListOf<any>
@ -48,11 +48,11 @@
}
} catch {}
}
function correctClass (clName: string): string {
function correctClass (clName: string): Ref<Class<Doc>> {
if (clName === 'contact:class:Employee') {
return 'contact:mixin:Employee'
return 'contact:mixin:Employee' as Ref<Class<Doc>>
}
return clName
return clName as Ref<Class<Doc>>
}
</script>
@ -126,17 +126,11 @@
</div>
{/if}
{:else if node.nodeName === 'SPAN'}
{#if node.getAttribute('data-objectclass') !== undefined && node.getAttribute('data-id') !== undefined}
<Component
is={view.component.ObjectPresenter}
inline
props={{
objectId: node.getAttribute('data-id'),
title: node.getAttribute('data-label'),
_class: correctClass(node.getAttribute('data-objectclass')),
inline: true
}}
/>
{@const objectId = node.getAttribute('data-id')}
{@const objectClass = node.getAttribute('data-objectclass')}
{#if objectClass !== undefined && objectId !== undefined}
<ObjectNode _id={objectId} _class={correctClass(objectClass)} title={node.getAttribute('data-label')} />
{:else}
<svelte:self nodes={node.childNodes} />
{/if}

View File

@ -0,0 +1,48 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, Ref } from '@hcengineering/core'
import { Component } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { createQuery } from '../../utils'
export let _id: Ref<Doc> | undefined = undefined
export let _class: Ref<Class<Doc>> | undefined = undefined
export let title: string = ''
const docQuery = createQuery()
let doc: Doc | undefined = undefined
$: if (_class != null && _id != null) {
docQuery.query(_class, { _id }, (r) => {
doc = r.shift()
})
}
</script>
{#if !doc}
<span class="antiMention">@{title}</span>
{:else}
<Component
is={view.component.ObjectMention}
showLoading={false}
props={{
object: doc,
title
}}
/>
{/if}

View File

@ -17,7 +17,7 @@
import { Organization } from '@hcengineering/contact'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { tooltip } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import Company from './icons/Company.svelte'
export let value: Organization
@ -29,18 +29,18 @@
</script>
{#if value}
<DocNavLink {disabled} {inline} object={value} {accent} {noUnderline}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
@{value.name}
</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
{:else}
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" style:max-width={maxWidth} use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="icon circle"><Company size={'small'} /></div>
<div class="icon circle">
<Company size={'small'} />
</div>
<span class="overflow-label label" class:no-underline={noUnderline || disabled} class:fs-bold={accent}
>{value.name}</span
>
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { Employee, Person } from '@hcengineering/contact'
import { IconSize, LabelAndProps, tooltip } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import Avatar from './Avatar.svelte'
export let value: Person | Employee | undefined | null
@ -35,12 +35,10 @@
</script>
{#if value}
<DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {inline} {colorInherit} {accent} noOverflow>
{#if inline}
<span class="antiMention" use:tooltip={disabled ? undefined : showTooltip}>
@{name}
</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} {colorInherit} onClick={onEdit} />
{:else}
<DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow>
<span
use:tooltip={disabled ? undefined : showTooltip}
class="antiPresenter"
@ -62,6 +60,6 @@
</span>
{/if}
</span>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -16,7 +16,7 @@
import { getName, Person } from '@hcengineering/contact'
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import type { LabelAndProps, IconSize } from '@hcengineering/ui'
import { personByIdStore, PersonLabelTooltip } from '..'
import { getPersonTooltip, personByIdStore, PersonLabelTooltip } from '..'
import PersonContent from './PersonContent.svelte'
import { getClient } from '@hcengineering/presentation'
import { Ref } from '@hcengineering/core'
@ -48,12 +48,9 @@
value: Person | null | undefined
): LabelAndProps | undefined {
if (!tooltipLabels) {
return !value
? undefined
: {
label: getEmbeddedLabel(getName(client.getHierarchy(), value))
}
return getPersonTooltip(client, value)
}
const direction = tooltipLabels?.direction
const component = value ? tooltipLabels.component : undefined
const label = value

View File

@ -119,6 +119,7 @@ import {
getCurrentEmployeeEmail,
getCurrentEmployeeName,
getCurrentEmployeePosition,
getPersonTooltip,
resolveLocation
} from './utils'
@ -373,7 +374,8 @@ export default async (): Promise<Resources> => ({
GetContactFirstName: getContactFirstName,
GetContactLastName: getContactLastName,
GetContactLink: getContactLink,
ContactTitleProvider: contactTitleProvider
ContactTitleProvider: contactTitleProvider,
PersonTooltipProvider: getPersonTooltip
},
resolver: {
Location: resolveLocation

View File

@ -15,9 +15,9 @@
//
import contact, { contactId } from '@hcengineering/contact'
import { type Doc } from '@hcengineering/core'
import { type Client, type Doc } from '@hcengineering/core'
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import { type Location } from '@hcengineering/ui'
import { type LabelAndProps, type Location } from '@hcengineering/ui'
import { type FilterFunction, type SortFunc } from '@hcengineering/view'
export default mergeIds(contactId, contact, {
@ -87,6 +87,7 @@ export default mergeIds(contactId, contact, {
FilterChannelInResult: '' as FilterFunction,
FilterChannelNinResult: '' as FilterFunction,
FilterChannelHasMessagesResult: '' as FilterFunction,
FilterChannelHasNewMessagesResult: '' as FilterFunction
FilterChannelHasNewMessagesResult: '' as FilterFunction,
PersonTooltipProvider: '' as Resource<(client: Client, doc?: Doc | null) => Promise<LabelAndProps | undefined>>
}
})

View File

@ -40,7 +40,7 @@ import {
toIdMap
} from '@hcengineering/core'
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform'
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { type TemplateDataProvider } from '@hcengineering/templates'
import {
@ -48,11 +48,13 @@ import {
type ResolvedLocation,
type TabItem,
getCurrentResolvedLocation,
getPanelURI
getPanelURI,
type LabelAndProps
} from '@hcengineering/ui'
import view, { type Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
import { derived, get, writable } from 'svelte/store'
import contact from './plugin'
export function formatDate (dueDateMs: Timestamp): string {
@ -377,3 +379,13 @@ export async function contactTitleProvider (client: Client, ref: Ref<Contact>, d
if (object === undefined) return ''
return getName(client.getHierarchy(), object)
}
export function getPersonTooltip (client: Client, value: Person | null | undefined): LabelAndProps | undefined {
const hierarchy = client.getHierarchy()
return value == null
? undefined
: {
label: getEmbeddedLabel(getName(hierarchy, value))
}
}

View File

@ -15,22 +15,20 @@
-->
<script lang="ts">
import { Category } from '@hcengineering/inventory'
import { DocNavLink } from '@hcengineering/view-resources'
import { tooltip } from '@hcengineering/ui'
import inventory from '../plugin'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
export let value: Category
export let inline: boolean = false
</script>
{#if value}
<DocNavLink object={value} {inline}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: inventory.string.Category }}>@{value.name}</span>
{:else}
{#if inline}
<ObjectMention object={value} />
{:else}
<DocNavLink object={value}>
<div class="flex-presenter overflow-label sm-tool-icon">
{value.name}
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -15,8 +15,9 @@
-->
<script lang="ts">
import { Product } from '@hcengineering/inventory'
import { Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { Icon } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import inventory from '../plugin'
export let value: Product
@ -24,14 +25,14 @@
</script>
{#if value}
<DocNavLink {inline} object={value}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: inventory.string.Product }}>@{value.name}</span>
{:else}
{#if inline}
<ObjectMention object={value} />
{:else}
<DocNavLink object={value}>
<div class="flex-presenter">
<div class="icon"><Icon icon={inventory.icon.Products} size={'small'} /></div>
<span class="label">{value.name}</span>
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -14,9 +14,11 @@
// limitations under the License.
//
import { type Doc } from '@hcengineering/core'
import { type Client, type Doc, type Ref } from '@hcengineering/core'
import { type Resources } from '@hcengineering/platform'
import { showPopup } from '@hcengineering/ui'
import { type Category, type Product } from '@hcengineering/inventory'
import Categories from './components/Categories.svelte'
import CategoryPresenter from './components/CategoryPresenter.svelte'
import CategoryRefPresenter from './components/CategoryRefPresenter.svelte'
@ -27,9 +29,22 @@ import VariantPresenter from './components/VariantPresenter.svelte'
import Variants from './components/Variants.svelte'
import CreateProduct from './components/CreateProduct.svelte'
import product from './plugin'
async function createSubcategory (object: Doc): Promise<void> {
showPopup(CreateCategory, { attachedTo: object._id })
}
async function getProductId (client: Client, ref: Ref<Product>, doc?: Product): Promise<string> {
const object = doc ?? (await client.findOne(product.class.Product, { _id: ref }))
if (object === undefined) return ''
return object.name
}
async function getCategoryId (client: Client, ref: Ref<Category>, doc?: Category): Promise<string> {
const object = doc ?? (await client.findOne(product.class.Category, { _id: ref }))
if (object === undefined) return ''
return object.name
}
export default async (): Promise<Resources> => ({
actionImpl: {
@ -44,5 +59,9 @@ export default async (): Promise<Resources> => ({
Variants,
VariantPresenter,
CreateProduct
},
function: {
ProductIdProvider: getProductId,
CategoryIdProvider: getCategoryId
}
})

View File

@ -15,8 +15,8 @@
-->
<script lang="ts">
import type { Lead } from '@hcengineering/lead'
import { Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { Icon } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import lead from '@hcengineering/lead'
export let value: Lead
@ -24,15 +24,14 @@
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let shouldShowAvatar: boolean = true
</script>
{#if value}
<DocNavLink object={value} {inline} {disabled} {noUnderline} {accent}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: lead.string.Lead }}>@{value.identifier}</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} />
{:else}
<DocNavLink object={value} {disabled} {noUnderline} {accent}>
<div class="flex-presenter">
{#if shouldShowAvatar}
<div class="icon"><Icon icon={lead.icon.Lead} size={'small'} /></div>
@ -41,6 +40,6 @@
>{value.identifier}</span
>
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -15,6 +15,7 @@
//
import { type Resources } from '@hcengineering/platform'
import CreateFunnel from './components/CreateFunnel.svelte'
import CreateLead from './components/CreateLead.svelte'
import EditLead from './components/EditLead.svelte'
@ -25,11 +26,12 @@ import LeadsPresenter from './components/LeadsPresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import CreateCustomer from './components/CreateCustomer.svelte'
import NewItemsHeader from './components/NewItemsHeader.svelte'
import { getLeadTitle } from './utils'
import EditFunnel from './components/EditFunnel.svelte'
import MyLeads from './components/MyLeads.svelte'
import TitlePresenter from './components/TitlePresenter.svelte'
import { getLeadId, getLeadTitle } from './utils'
export default async (): Promise<Resources> => ({
component: {
CreateFunnel,
@ -47,6 +49,7 @@ export default async (): Promise<Resources> => ({
TitlePresenter
},
function: {
LeadTitleProvider: getLeadTitle
LeadTitleProvider: getLeadTitle,
LeadIdProvider: getLeadId
}
})

View File

@ -53,6 +53,7 @@ export default mergeIds(leadId, lead, {
TitlePresenter: '' as AnyComponent
},
function: {
LeadTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
LeadTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
LeadIdProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
}
})

View File

@ -7,3 +7,9 @@ export async function getLeadTitle (client: TxOperations, ref: Ref<Doc>, doc?: L
if (object === undefined) throw new Error(`Lead not found, _id: ${ref}`)
return `LEAD-${object.number}`
}
export async function getLeadId (client: TxOperations, ref: Ref<Lead>, doc?: Lead): Promise<string> {
const object = doc ?? (await client.findOne(lead.class.Lead, { _id: ref }))
if (object === undefined) throw new Error(`Lead not found, _id: ${ref}`)
return object.identifier
}

View File

@ -17,9 +17,8 @@
import { getClient } from '@hcengineering/presentation'
import type { Applicant } from '@hcengineering/recruit'
import recruit from '@hcengineering/recruit'
import recruitPlg from '../plugin'
import { Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { Icon } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
export let value: Applicant
export let inline: boolean = false
@ -33,12 +32,10 @@
</script>
{#if value && shortLabel}
<DocNavLink object={value} {inline} {disabled} {noUnderline} {accent}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: recruitPlg.string.Application }}>
@{#if shortLabel}{shortLabel}-{/if}{value.number}
</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} />
{:else}
<DocNavLink object={value} {disabled} {noUnderline} {accent}>
<div class="flex-presenter">
{#if shouldShowAvatar}
<div class="icon">
@ -49,6 +46,6 @@
{#if shortLabel}{shortLabel}-{/if}{value.number}
</span>
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -15,11 +15,11 @@
-->
<script lang="ts">
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Vacancy } from '@hcengineering/recruit'
import { Icon, getPlatformAvatarColorForTextDef, themeStore, tooltip } from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import recruit from '../plugin'
export let value: Vacancy
@ -38,18 +38,16 @@
</script>
{#if value}
<DocNavLink {disabled} object={value} {inline} {accent} {noUnderline} component={recruit.component.EditVacancy}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: recruit.string.Vacancy }}>
@{value.name}
</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} component={recruit.component.EditVacancy} />
{:else}
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={recruit.component.EditVacancy}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="icon"><Icon icon={recruit.icon.Vacancy} size={'small'} /></div>
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name}
</span>
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -17,9 +17,9 @@
import { translate } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Component } from '@hcengineering/tracker'
import { Icon, Component as UIComponent, themeStore, tooltip } from '@hcengineering/ui'
import { Icon, Component as UIComponent, themeStore } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import tracker from '../../plugin'
export let value: WithLookup<Component> | undefined
@ -53,10 +53,10 @@
</script>
<div class="flex-row-center">
<DocNavLink object={value} {onClick} {disabled} {noUnderline} {inline} {accent} component={view.component.EditDoc}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: tracker.string.Component }}>@{label}</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} {onClick} />
{:else}
<DocNavLink object={value} {onClick} {disabled} {noUnderline} {accent} component={view.component.EditDoc}>
<span class="flex-presenter flex-row-center" class:list={kind === 'list'}>
<div class="flex-row-center">
{#if shouldShowAvatar}
@ -69,8 +69,8 @@
</span>
</div>
</span>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{#if presenters.length > 0}
<div class="flex-row-center">

View File

@ -21,7 +21,8 @@
import type { Issue } from '@hcengineering/tracker'
import { AnySvelteComponent, Component, Icon, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import tracker from '../../plugin'
export let value: WithLookup<Issue> | undefined
@ -41,19 +42,7 @@
</script>
{#if inline && value}
<DocNavLink
object={value}
{onClick}
{disabled}
{noUnderline}
{inline}
component={tracker.component.EditIssue}
shrink={0}
>
{#if inline}
<span class="antiMention" use:tooltip={{ label: tracker.string.Issue }}>@{value.identifier}</span>
{/if}
</DocNavLink>
<ObjectMention object={value} {disabled} {noUnderline} {onClick} component={tracker.component.EditIssue} />
{#if presenters.length > 0}
<div class="flex-row-center">
{#each presenters as mixinPresenter}
@ -63,15 +52,7 @@
{/if}
{:else if value}
<div class="flex-row-center">
<DocNavLink
object={value}
{onClick}
{disabled}
{noUnderline}
{inline}
component={tracker.component.EditIssue}
shrink={0}
>
<DocNavLink object={value} {onClick} {disabled} {noUnderline} component={tracker.component.EditIssue} shrink={0}>
<span class="issuePresenterRoot" class:list={kind === 'list'} class:cursor-pointer={!disabled}>
{#if shouldShowAvatar}
<div class="icon" use:tooltip={{ label: tracker.string.Issue }}>

View File

@ -22,8 +22,9 @@
themeStore,
tooltip
} from '@hcengineering/ui'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import tracker from '../../plugin'
export let value: WithLookup<Milestone> | undefined
@ -47,10 +48,10 @@
</script>
{#if value}
<DocNavLink object={value} {disabled} {inline} {accent} {noUnderline} {onClick}>
{#if inline}
<span class="antiMention" use:tooltip={{ label: tracker.string.Milestone }}>@{value.label}</span>
{:else}
{#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} {onClick} />
{:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline} {onClick}>
<div class="flex-presenter" use:tooltip={{ label: tracker.string.Milestone }}>
{#if shouldShowAvatar}
<div class="icon">
@ -66,6 +67,6 @@
{value.label}
</span>
</div>
{/if}
</DocNavLink>
</DocNavLink>
{/if}
{/if}

View File

@ -14,11 +14,13 @@ export function isIssueId (shortLink: string): boolean {
return /^\S+-\d+$/.test(shortLink)
}
export async function issueIdentifierProvider (client: TxOperations, ref: Ref<Doc>): Promise<string> {
const object = await client.findOne(tracker.class.Issue, { _id: ref as Ref<Issue> })
export async function issueIdentifierProvider (client: TxOperations, ref: Ref<Issue>, doc?: Issue): Promise<string> {
const object = doc ?? (await client.findOne(tracker.class.Issue, { _id: ref }))
if (object === undefined) {
return ''
}
return object.identifier
}

View File

@ -0,0 +1,125 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, Ref } from '@hcengineering/core'
import { AnyComponent, LabelAndProps, themeStore, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { getResource, translate } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import DocNavLink from './DocNavLink.svelte'
import { getDocIdentifier } from '../utils'
export let _id: Ref<Doc> | undefined = undefined
export let _class: Ref<Class<Doc>> | undefined = undefined
export let object: Doc | undefined | null
export let title: string = ''
export let component: AnyComponent | undefined = undefined
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let colorInherit: boolean = false
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const docQuery = createQuery()
let doc: Doc | undefined = object ?? undefined
let docLabel: string = ''
let docTitle: string | undefined = undefined
let docTooltip: LabelAndProps = {}
let docComponent: AnyComponent
let displayTitle = ''
$: displayTitle = docTitle || title || docLabel
$: docComponent = getPanelComponent(doc, _class)
$: if (object == null && _class != null && _id != null) {
docQuery.query(_class, { _id }, (r) => {
doc = r.shift()
})
} else if (object != null) {
docQuery.unsubscribe()
doc = object
}
$: void updateDocTitle(doc)
$: void updateDocTooltip(doc)
$: void updateDocLabel(doc, _class)
function getPanelComponent (doc?: Doc, _class?: Ref<Class<Doc>>): AnyComponent {
if (component !== undefined) {
return component
}
const resultClass = doc?._class ?? _class
if (resultClass === undefined) {
return view.component.EditDoc
} else {
const panelComponent = hierarchy.classHierarchyMixin(resultClass, view.mixin.ObjectPanel)
return panelComponent?.component ?? view.component.EditDoc
}
}
async function updateDocLabel (doc?: Doc, _class?: Ref<Class<Doc>>): Promise<void> {
const resultClass = doc?._class ?? _class
docLabel = resultClass ? await translate(hierarchy.getClass(resultClass).label, {}, $themeStore.language) : ''
}
async function updateDocTitle (doc: Doc | undefined): Promise<void> {
docTitle = doc ? await getDocIdentifier(client, doc._id, doc._class, doc) : undefined
}
async function updateDocTooltip (doc?: Doc): Promise<void> {
if (doc === undefined) {
docTooltip = {}
return
}
const mixin = hierarchy.classHierarchyMixin(doc._class, view.mixin.ObjectTooltip)
if (mixin?.provider !== undefined) {
const providerFn = await getResource(mixin.provider)
docTooltip = (await providerFn(client, doc)) ?? { label: hierarchy.getClass(doc._class).label }
} else {
docTooltip = { label: hierarchy.getClass(doc._class).label }
}
}
</script>
{#if displayTitle}
<DocNavLink
object={doc}
component={docComponent}
{disabled}
{accent}
{colorInherit}
{noUnderline}
inline
noOverflow
{onClick}
>
<span class="antiMention" class:reference={!disabled} use:tooltip={disabled ? undefined : docTooltip}>
@{displayTitle}
</span>
</DocNavLink>
{/if}

View File

@ -87,6 +87,7 @@ import ArrayFilter from './components/filter/ArrayFilter.svelte'
import SpaceHeader from './components/SpaceHeader.svelte'
import ViewletContentView from './components/ViewletContentView.svelte'
import AttachedDocPanel from './components/AttachedDocPanel.svelte'
import ObjectMention from './components/ObjectMention.svelte'
import {
afterResult,
@ -193,7 +194,8 @@ export {
SpaceHeader,
ViewletContentView,
HyperlinkEditor,
IconPicker
IconPicker,
ObjectMention
}
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
@ -256,7 +258,8 @@ export default async (): Promise<Resources> => ({
StatusRefPresenter,
DateFilterPresenter,
StringFilterPresenter,
AttachedDocPanel
AttachedDocPanel,
ObjectMention
},
popup: {
PositionElementAlignment

View File

@ -50,6 +50,7 @@ import {
ObjectPanel,
ObjectPresenter,
ObjectTitle,
ObjectTooltip,
ObjectValidator,
PreviewPresenter,
SpaceHeader,
@ -91,6 +92,7 @@ const view = plugin(viewId, {
ObjectFactory: '' as Ref<Mixin<ObjectFactory>>,
ObjectTitle: '' as Ref<Mixin<ObjectTitle>>,
ObjectIdentifier: '' as Ref<Mixin<ObjectIdentifier>>,
ObjectTooltip: '' as Ref<Mixin<ObjectTooltip>>,
SpaceHeader: '' as Ref<Mixin<SpaceHeader>>,
SpaceName: '' as Ref<Mixin<SpaceName>>,
IgnoreActions: '' as Ref<Mixin<IgnoreActions>>,
@ -153,7 +155,8 @@ const view = plugin(viewId, {
GrowPresenter: '' as AnyComponent,
DividerPresenter: '' as AnyComponent,
IconWithEmoji: '' as AnyComponent,
AttachedDocPanel: '' as AnyComponent
AttachedDocPanel: '' as AnyComponent,
ObjectMention: '' as AnyComponent
},
ids: {
IconWithEmoji: '' as Asset

View File

@ -40,7 +40,13 @@ import {
UXObject,
WithLookup
} from '@hcengineering/core'
import { AnyComponent, AnySvelteComponent, Location, Location as PlatformLocation } from '@hcengineering/ui'
import {
AnyComponent,
AnySvelteComponent,
type LabelAndProps,
Location,
Location as PlatformLocation
} from '@hcengineering/ui'
import { Asset, IntlString, Resource, Status } from '@hcengineering/platform'
import { Preference } from '@hcengineering/preference'
@ -279,6 +285,13 @@ export interface ObjectIdentifier extends Class<Doc> {
provider: Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>
}
/**
* @public
*/
export interface ObjectTooltip extends Class<Doc> {
provider: Resource<(client: Client, doc?: Doc | null) => Promise<LabelAndProps | undefined>>
}
/**
* @public
*/