Mention object icons & bug fixes (#8357)

* Mention object icons & bug fixes

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* ff

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* fmt

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* ff

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* rv

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* ff

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* fmt

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

* fixed test

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>

---------

Signed-off-by: Victor Ilyushchenko <alt13ri@gmail.com>
This commit is contained in:
Victor Ilyushchenko 2025-03-27 18:33:19 +03:00 committed by GitHub
parent 62d6a76b4a
commit 0bbf5773e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 171 additions and 115 deletions

View File

@ -394,6 +394,10 @@ export function createModel (builder: Builder): void {
presenter: documents.component.DocumentMetaPresenter presenter: documents.component.DocumentMetaPresenter
}) })
builder.mixin(documents.class.DocumentMeta, core.class.Class, view.mixin.ObjectTitle, {
titleProvider: documents.function.DocumentMetaTitleProvider
})
builder.mixin(documents.class.DocumentMeta, core.class.Class, view.mixin.LinkProvider, { builder.mixin(documents.class.DocumentMeta, core.class.Class, view.mixin.LinkProvider, {
encode: documents.function.GetDocumentMetaLinkFragment encode: documents.function.GetDocumentMetaLinkFragment
}) })

View File

@ -145,7 +145,7 @@ export class TProject extends TDoc implements Project {
} }
@Model(documents.class.DocumentMeta, core.class.Doc, DOMAIN_DOCUMENTS) @Model(documents.class.DocumentMeta, core.class.Doc, DOMAIN_DOCUMENTS)
@UX(documents.string.Document) @UX(documents.string.ControlledDocument, documents.icon.Document)
export class TDocumentMeta extends TDoc implements DocumentMeta { export class TDocumentMeta extends TDoc implements DocumentMeta {
@Prop(Collection(documents.class.Document), documents.string.Documents) @Prop(Collection(documents.class.Document), documents.string.Documents)
documents!: CollectionSize<Document> documents!: CollectionSize<Document>

View File

@ -111,7 +111,7 @@ export class TResource extends TDoc implements Resource {
} }
@Model(drive.class.Folder, drive.class.Resource, DOMAIN_DRIVE) @Model(drive.class.Folder, drive.class.Resource, DOMAIN_DRIVE)
@UX(drive.string.Folder) @UX(drive.string.Folder, drive.icon.Folder)
export class TFolder extends TResource implements Folder { export class TFolder extends TResource implements Folder {
@Prop(TypeRef(drive.class.Folder), drive.string.Parent) @Prop(TypeRef(drive.class.Folder), drive.string.Parent)
@Index(IndexKind.Indexed) @Index(IndexKind.Indexed)
@ -126,7 +126,7 @@ export class TFolder extends TResource implements Folder {
} }
@Model(drive.class.File, drive.class.Resource, DOMAIN_DRIVE) @Model(drive.class.File, drive.class.Resource, DOMAIN_DRIVE)
@UX(drive.string.File) @UX(drive.string.File, drive.icon.File)
export class TFile extends TResource implements File { export class TFile extends TResource implements File {
@Prop(TypeRef(drive.class.Folder), drive.string.Parent) @Prop(TypeRef(drive.class.Folder), drive.string.Parent)
@Index(IndexKind.Indexed) @Index(IndexKind.Indexed)

View File

@ -26,6 +26,7 @@
export let shrink: number = 1 export let shrink: number = 1
export let accent: boolean = false export let accent: boolean = false
export let noOverflow: boolean = false export let noOverflow: boolean = false
export let inlineReference: boolean = false
function clickHandler (e: MouseEvent): void { function clickHandler (e: MouseEvent): void {
if (disabled) return if (disabled) return
@ -72,6 +73,7 @@
class:noOverflow class:noOverflow
class:inline class:inline
class:colorInherit class:colorInherit
class:antiMention={inlineReference}
class:fs-bold={accent} class:fs-bold={accent}
style:flex-shrink={shrink} style:flex-shrink={shrink}
on:click={clickHandler} on:click={clickHandler}
@ -85,6 +87,7 @@
class:noOverflow class:noOverflow
class:inline class:inline
class:colorInherit class:colorInherit
class:antiMention={inlineReference}
class:fs-bold={accent} class:fs-bold={accent}
style:flex-shrink={shrink} style:flex-shrink={shrink}
on:click={clickHandler} on:click={clickHandler}
@ -95,7 +98,7 @@
<style lang="scss"> <style lang="scss">
span, span,
a { a:not(.antiMention) {
min-width: 0; min-width: 0;
font-weight: inherit; font-weight: inherit;

View File

@ -14,19 +14,23 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Class, Doc, Ref } from '@hcengineering/core' import { Class, Doc, Ref } from '@hcengineering/core'
import { Component } from '@hcengineering/ui' import { Component, Icon } from '@hcengineering/ui'
import view from '@hcengineering/view' import view from '@hcengineering/view'
import { createQuery } from '../../utils' import { createQuery, getClient } from '../../utils'
export let _id: Ref<Doc> | undefined = undefined export let _id: Ref<Doc> | undefined = undefined
export let _class: Ref<Class<Doc>> | undefined = undefined export let _class: Ref<Class<Doc>> | undefined = undefined
export let title: string = '' export let title: string = ''
const client = getClient()
const hierarchy = client.getHierarchy()
const docQuery = createQuery() const docQuery = createQuery()
let doc: Doc | undefined = undefined let doc: Doc | undefined = undefined
$: icon = _class !== undefined ? hierarchy.getClass(_class).icon : null
$: if (_class != null && _id != null) { $: if (_class != null && _id != null) {
docQuery.query(_class, { _id }, (r) => { docQuery.query(_class, { _id }, (r) => {
doc = r.shift() doc = r.shift()
@ -35,7 +39,9 @@
</script> </script>
{#if !doc} {#if !doc}
<span class="antiMention">@{title}</span> <span class="antiMention">
{#if icon}<Icon {icon} size="small" />{' '}{:else}@{/if}{title}
</span>
{:else} {:else}
<Component <Component
is={view.component.ObjectMention} is={view.component.ObjectMention}

View File

@ -21,7 +21,7 @@ describe('dsl', () => {
) )
) )
expect(jsonToHTML(doc)).toEqual( expect(jsonToHTML(doc)).toEqual(
'<p>Hello, <span data-type="reference" class="antiMention" data-id="123" data-objectclass="world" data-label="World">@World</span></p><p>Check out <a target="_blank" rel="noopener noreferrer" class="cursor-pointer" href="https://example.com"><u>this link</u></a>.</p>' '<p>Hello, <span data-type="reference" data-id="123" data-objectclass="world" data-label="World" class="antiMention">@World</span></p><p>Check out <a target="_blank" rel="noopener noreferrer" class="cursor-pointer" href="https://example.com"><u>this link</u></a>.</p>'
) )
}) })
}) })

View File

@ -16,6 +16,7 @@
import { Node, mergeAttributes } from '@tiptap/core' import { Node, mergeAttributes } from '@tiptap/core'
import { getDataAttribute } from './utils' import { getDataAttribute } from './utils'
import { Class, Doc, Ref } from '@hcengineering/core' import { Class, Doc, Ref } from '@hcengineering/core'
import { Attrs } from '@tiptap/pm/model'
export interface ReferenceNodeProps { export interface ReferenceNodeProps {
id: Ref<Doc> id: Ref<Doc>
@ -24,7 +25,6 @@ export interface ReferenceNodeProps {
} }
export interface ReferenceOptions { export interface ReferenceOptions {
renderLabel: (props: { options: ReferenceOptions, props: ReferenceNodeProps }) => string
suggestion: { char?: string } suggestion: { char?: string }
HTMLAttributes: Record<string, any> HTMLAttributes: Record<string, any>
} }
@ -37,8 +37,6 @@ export const ReferenceNode = Node.create<ReferenceOptions>({
group: 'inline', group: 'inline',
inline: true, inline: true,
selectable: true, selectable: true,
atom: true,
draggable: true,
addAttributes () { addAttributes () {
return { return {
@ -50,10 +48,6 @@ export const ReferenceNode = Node.create<ReferenceOptions>({
addOptions () { addOptions () {
return { return {
renderLabel ({ options, props }) {
// eslint-disable-next-line
return `${options.suggestion.char}${props.label ?? props.id}`
},
suggestion: { char: '@' }, suggestion: { char: '@' },
HTMLAttributes: {} HTMLAttributes: {}
} }
@ -62,22 +56,14 @@ export const ReferenceNode = Node.create<ReferenceOptions>({
parseHTML () { parseHTML () {
return [ return [
{ {
tag: `span[data-type="${this.name}"]`, priority: 60,
getAttrs: (el) => { tag: 'span[data-type="reference"]',
const id = (el as HTMLSpanElement).getAttribute('id')?.trim() getAttrs
const label = (el as HTMLSpanElement).getAttribute('label')?.trim() },
const objectclass = (el as HTMLSpanElement).getAttribute('objectclass')?.trim() {
priority: 60,
if (id == null || label == null || objectclass == null) { tag: 'a[data-type="reference"]',
return false getAttrs
}
return {
id,
label,
objectclass
}
}
} }
] ]
}, },
@ -88,20 +74,31 @@ export const ReferenceNode = Node.create<ReferenceOptions>({
mergeAttributes( mergeAttributes(
{ {
'data-type': this.name, 'data-type': this.name,
'data-id': node.attrs.id,
'data-objectclass': node.attrs.objectclass,
'data-label': node.attrs.label,
class: 'antiMention' class: 'antiMention'
}, },
this.options.HTMLAttributes, this.options.HTMLAttributes,
HTMLAttributes HTMLAttributes
), ),
this.options.renderLabel({ `${this.options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
options: this.options,
props: node.attrs as ReferenceNodeProps
})
] ]
},
renderText ({ node }) {
const options = this.options
return options.renderLabel({ options, props: node.attrs as ReferenceNodeProps })
} }
}) })
function getAttrs (el: HTMLSpanElement): Attrs | false {
const id = el.dataset.id?.trim()
const label = el.dataset.label?.trim()
const objectclass = el.dataset.objectclass?.trim()
if (id == null || label == null || objectclass == null) {
return false
}
return {
id,
label,
objectclass
}
}

View File

@ -518,14 +518,28 @@
} }
} }
.antiMention { .antiMention, .antiMention:visited {
display: inline-flex; display: inline;
font-weight: normal;
padding: 0 .25rem; padding: 0 .25rem;
width: fit-content;
color: var(--theme-link-color); color: var(--theme-link-color);
background-color: var(--theme-mention-bg-color); background-color: var(--theme-mention-bg-color);
text-decoration: none;
border-radius: .25rem; border-radius: .25rem;
cursor: pointer; cursor: pointer;
user-select: text;
&:hover {
text-decoration: none !important;
background-color: var(--theme-mention-focused-bg-color);
}
> svg {
display: inline-block;
vertical-align: sub;
margin-bottom: 1px;
width: .875rem;
}
} }
.antiDivider { .antiDivider {

View File

@ -36,7 +36,7 @@
</script> </script>
{#if inline && value} {#if inline && value}
<ObjectMention object={value} {disabled} {noUnderline} {onClick} component={card.component.EditCard} /> <ObjectMention object={value} {disabled} {onClick} component={card.component.EditCard} />
{:else if value} {:else if value}
{#if type === 'link'} {#if type === 'link'}
<div class="flex-row-center"> <div class="flex-row-center">

View File

@ -34,13 +34,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention <ObjectMention object={value} {disabled} component={contact.component.EditOrganizationPanel} />
object={value}
{disabled}
{accent}
{noUnderline}
component={contact.component.EditOrganizationPanel}
/>
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={contact.component.EditOrganizationPanel}> <DocNavLink {disabled} object={value} {accent} {noUnderline} component={contact.component.EditOrganizationPanel}>
<div class="flex-presenter" style:max-width={maxWidth} use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" style:max-width={maxWidth} use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -41,7 +41,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} {colorInherit} onClick={onEdit} /> <ObjectMention object={value} {disabled} onClick={onEdit} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow> <DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow>
<span <span

View File

@ -114,7 +114,8 @@ import {
getVisibleFilters, getVisibleFilters,
isFolder, isFolder,
renameFolder, renameFolder,
sortDocumentStates sortDocumentStates,
getDocumentMetaTitle
} from './utils' } from './utils'
export { DocumentStatusTag, DocumentTitle, DocumentVersionPresenter, StatePresenter } export { DocumentStatusTag, DocumentTitle, DocumentVersionPresenter, StatePresenter }
@ -462,6 +463,7 @@ export default async (): Promise<Resources> => ({
ControlledDocumentReferenceObjectProvider: controlledDocumentReferenceObjectProvider, ControlledDocumentReferenceObjectProvider: controlledDocumentReferenceObjectProvider,
ProjectDocumentReferenceObjectProvider: projectDocumentReferenceObjectProvider, ProjectDocumentReferenceObjectProvider: projectDocumentReferenceObjectProvider,
ControlledDocumentTitleProvider: getControlledDocumentTitle, ControlledDocumentTitleProvider: getControlledDocumentTitle,
DocumentMetaTitleProvider: getDocumentMetaTitle,
Comment: comment, Comment: comment,
IsCommentVisible: isCommentVisible IsCommentVisible: isCommentVisible
}, },

View File

@ -249,6 +249,7 @@ export default mergeIds(documentsId, documents, {
CanOpenDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>, CanOpenDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
CanPrintDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>, CanPrintDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
CanTransferDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>, CanTransferDocument: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
ControlledDocumentTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>> ControlledDocumentTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>,
DocumentMetaTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
} }
}) })

View File

@ -716,6 +716,18 @@ export async function documentIdentifierProvider (client: Client, ref: Ref<Docum
return document.code return document.code
} }
export async function getDocumentMetaTitle (
client: Client,
ref: Ref<DocumentMeta>,
doc?: DocumentMeta
): Promise<string> {
const object = doc ?? (await client.findOne(documents.class.DocumentMeta, { _id: ref }))
if (object === undefined) return ''
return object.title
}
export async function controlledDocumentReferenceObjectProvider ( export async function controlledDocumentReferenceObjectProvider (
client: Client, client: Client,
ref: Ref<ControlledDocument>, ref: Ref<ControlledDocument>,

View File

@ -37,7 +37,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} component={document.component.EditDoc} /> <ObjectMention object={value} {disabled} component={document.component.EditDoc} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={document.component.EditDoc}> <DocNavLink {disabled} object={value} {accent} {noUnderline} component={document.component.EditDoc}>
<div <div

View File

@ -34,7 +34,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline}> <DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -39,7 +39,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>

View File

@ -53,7 +53,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} onClick={handleClick} {disabled} {accent} {noUnderline}> <DocNavLink object={value} onClick={handleClick} {disabled} {accent} {noUnderline}>
<div class="flex-presenter"> <div class="flex-presenter">

View File

@ -34,7 +34,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline}> <DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>

View File

@ -32,7 +32,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} {disabled} {noUnderline} {accent}> <DocNavLink object={value} {disabled} {noUnderline} {accent}>
<div class="flex-presenter"> <div class="flex-presenter">

View File

@ -31,7 +31,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>

View File

@ -36,7 +36,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(roomName) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(roomName) }}>

View File

@ -46,7 +46,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} title={name} /> <ObjectMention object={value} {disabled} title={name} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(name) }}>

View File

@ -32,7 +32,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} title={version} /> <ObjectMention object={value} {disabled} title={version} />
{:else} {:else}
<DocNavLink object={value} {disabled} {noUnderline} {accent}> <DocNavLink object={value} {disabled} {noUnderline} {accent}>
<div class="flex-presenter"> <div class="flex-presenter">

View File

@ -35,7 +35,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -35,7 +35,7 @@
{#if value && shortLabel} {#if value && shortLabel}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} /> <ObjectMention object={value} {disabled} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} {disabled} {noUnderline} {accent}> <DocNavLink object={value} {disabled} {noUnderline} {accent}>
<div class="flex-presenter"> <div class="flex-presenter">

View File

@ -41,7 +41,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} component={recruit.component.EditVacancy} /> <ObjectMention object={value} {disabled} component={recruit.component.EditVacancy} />
{:else if type === 'link'} {:else if type === 'link'}
<div class="flex-between flex-gap-2 w-full"> <div class="flex-between flex-gap-2 w-full">
<DocNavLink {disabled} object={value} {accent} {noUnderline} component={recruit.component.EditVacancy}> <DocNavLink {disabled} object={value} {accent} {noUnderline} component={recruit.component.EditVacancy}>

View File

@ -35,7 +35,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} {colorInherit} onClick={onEdit} /> <ObjectMention object={value} {disabled} onClick={onEdit} />
{:else if type === 'link'} {:else if type === 'link'}
<DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow> <DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow>
<div class="flex-presenter" style:max-width={maxWidth}> <div class="flex-presenter" style:max-width={maxWidth}>

View File

@ -31,7 +31,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention title={value?.name} object={value} {disabled} {accent} {noUnderline} /> <ObjectMention title={value?.name} object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline} {onClick}> <DocNavLink object={value} {disabled} {accent} {noUnderline} {onClick}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -37,7 +37,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: testManagement.string.TestCase }}> <div class="flex-presenter" use:tooltip={{ label: testManagement.string.TestCase }}>

View File

@ -27,7 +27,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -37,7 +37,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: testManagement.string.TestResult }}> <div class="flex-presenter" use:tooltip={{ label: testManagement.string.TestResult }}>

View File

@ -27,7 +27,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -30,7 +30,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} /> <ObjectMention object={value} {disabled} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline}> <DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}> <div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>

View File

@ -26,6 +26,7 @@ import { getMetadata, getResource } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import view from '@hcengineering/view' import view from '@hcengineering/view'
import contact from '@hcengineering/contact'
import { parseLocation, type Location } from '@hcengineering/ui' import { parseLocation, type Location } from '@hcengineering/ui'
import workbench, { type Application } from '@hcengineering/workbench' import workbench, { type Application } from '@hcengineering/workbench'
@ -38,10 +39,6 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
addOptions () { addOptions () {
return { return {
HTMLAttributes: {}, HTMLAttributes: {},
renderLabel ({ options, props }) {
// eslint-disable-next-line
return `${options.suggestion.char}${props.label ?? props.id}`
},
suggestion: { suggestion: {
char: '@', char: '@',
allowSpaces: true, allowSpaces: true,
@ -86,19 +83,21 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
addNodeView () { addNodeView () {
return ({ node, HTMLAttributes }) => { return ({ node, HTMLAttributes }) => {
const span = document.createElement('span') const root = document.createElement('span')
span.setAttribute('data-type', this.name) root.className = 'antiMention'
span.className = 'antimention'
const attributes = mergeAttributes( const attributes = mergeAttributes(
{ {
'data-type': this.name, 'data-type': this.name,
'data-id': node.attrs.id,
'data-objectclass': node.attrs.objectclass,
'data-label': node.attrs.label,
class: 'antiMention' class: 'antiMention'
}, },
this.options.HTMLAttributes, this.options.HTMLAttributes,
HTMLAttributes HTMLAttributes
) )
span.addEventListener('click', (event) => { root.addEventListener('click', (event) => {
if (event.button !== 0) return if (event.button !== 0) return
const link = (event.target as HTMLElement)?.closest('span') const link = (event.target as HTMLElement)?.closest('span')
@ -112,20 +111,40 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
}) })
Object.entries(attributes).forEach(([key, value]) => { Object.entries(attributes).forEach(([key, value]) => {
span.setAttribute(key, value) root.setAttribute(key, value)
}) })
const client = getClient()
const hierarchy = client.getHierarchy()
const query = createQuery(true) const query = createQuery(true)
const options = this.options const options = this.options
const renderLabel = (props: ReferenceNodeProps): void => { const renderLabel = (props: ReferenceNodeProps): void => {
span.setAttribute('data-label', props.label) root.setAttribute('data-label', props.label)
span.innerText = options.renderLabel({ options, props: props ?? (node.attrs as ReferenceNodeProps) }) titleSpan.innerText = `${iconUrl !== '' ? '' : options.suggestion.char}${props.label ?? props.id}`
} }
const id = node.attrs.id const id = node.attrs.id
const objectclass: Ref<Class<Doc>> = node.attrs.objectclass const objectclass: Ref<Class<Doc>> = node.attrs.objectclass
const icon =
objectclass !== undefined && !hierarchy.isDerived(objectclass, contact.class.Contact)
? hierarchy.getClass(objectclass).icon
: undefined
const iconUrl = typeof icon === 'string' ? getMetadata(icon) ?? 'https://anticrm.org/logo.svg' : ''
if (iconUrl !== '') {
const svg = root.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))
root.appendChild(document.createTextNode(' '))
svg.setAttribute('class', 'svg-small')
svg.setAttribute('fill', 'currentColor')
const use = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'use'))
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', iconUrl)
}
const titleSpan = root.appendChild(document.createElement('span'))
renderLabel({ id, objectclass, label: node.attrs.label }) renderLabel({ id, objectclass, label: node.attrs.label })
if (id !== undefined && objectclass !== undefined) { if (id !== undefined && objectclass !== undefined) {
@ -141,7 +160,7 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
} }
return { return {
dom: span, dom: root,
update (node, decorations) { update (node, decorations) {
renderLabel({ id, objectclass, label: node.attrs.label }) renderLabel({ id, objectclass, label: node.attrs.label })
return true return true

View File

@ -44,7 +44,7 @@
<div class="flex-row-center"> <div class="flex-row-center">
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} {onClick} /> <ObjectMention object={value} {disabled} {onClick} />
{:else} {:else}
<DocNavLink object={value} {onClick} {disabled} {noUnderline} {accent} component={view.component.EditDoc}> <DocNavLink object={value} {onClick} {disabled} {noUnderline} {accent} component={view.component.EditDoc}>
<span class="flex-presenter flex-row-center" class:list={kind === 'list'}> <span class="flex-presenter flex-row-center" class:list={kind === 'list'}>

View File

@ -40,7 +40,7 @@
</script> </script>
{#if inline && value} {#if inline && value}
<ObjectMention object={value} {disabled} {noUnderline} {onClick} component={tracker.component.EditIssue} /> <ObjectMention object={value} {disabled} {onClick} component={tracker.component.EditIssue} />
{:else if value} {:else if value}
{#if type === 'link'} {#if type === 'link'}
<div class="flex-row-center"> <div class="flex-row-center">

View File

@ -49,7 +49,7 @@
{#if value} {#if value}
{#if inline} {#if inline}
<ObjectMention object={value} {disabled} {noUnderline} {accent} {onClick} /> <ObjectMention object={value} {disabled} {onClick} />
{:else} {:else}
<DocNavLink object={value} {disabled} {accent} {noUnderline} {onClick}> <DocNavLink object={value} {disabled} {accent} {noUnderline} {onClick}>
<div class="flex-presenter" use:tooltip={{ label: tracker.string.Milestone }}> <div class="flex-presenter" use:tooltip={{ label: tracker.string.Milestone }}>

View File

@ -31,6 +31,7 @@
export let shrink: number = 1 export let shrink: number = 1
export let accent: boolean = false export let accent: boolean = false
export let noOverflow: boolean = false export let noOverflow: boolean = false
export let inlineReference: boolean = false
let _disabled = disabled || $restrictionStore.disableNavigation let _disabled = disabled || $restrictionStore.disableNavigation
$: _disabled = disabled || $restrictionStore.disableNavigation $: _disabled = disabled || $restrictionStore.disableNavigation
@ -58,6 +59,17 @@
$: if (object !== undefined) getHref(object) $: if (object !== undefined) getHref(object)
</script> </script>
<NavLink disabled={_disabled} {onClick} {noUnderline} {inline} {shrink} {href} {colorInherit} {accent} {noOverflow}> <NavLink
disabled={_disabled}
{onClick}
{noUnderline}
{inline}
{shrink}
{href}
{colorInherit}
{accent}
{noOverflow}
{inlineReference}
>
<slot /> <slot />
</NavLink> </NavLink>

View File

@ -16,11 +16,13 @@
import { Class, Doc, Ref } from '@hcengineering/core' import { Class, Doc, Ref } from '@hcengineering/core'
import { getResource, translateCB } from '@hcengineering/platform' import { getResource, translateCB } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import { AnyComponent, LabelAndProps, themeStore, tooltip } from '@hcengineering/ui' import { AnyComponent, Icon, LabelAndProps, themeStore, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view' import view from '@hcengineering/view'
import { getReferenceLabel } from '@hcengineering/text-editor-resources/src/components/extension/reference' import { getReferenceLabel } from '@hcengineering/text-editor-resources/src/components/extension/reference'
import { classIcon } from '../utils'
import DocNavLink from './DocNavLink.svelte' import DocNavLink from './DocNavLink.svelte'
import contact from '@hcengineering/contact'
export let _id: Ref<Doc> | undefined = undefined export let _id: Ref<Doc> | undefined = undefined
export let _class: Ref<Class<Doc>> | undefined = undefined export let _class: Ref<Class<Doc>> | undefined = undefined
@ -28,9 +30,6 @@
export let title: string = '' export let title: string = ''
export let component: AnyComponent | undefined = undefined export let component: AnyComponent | undefined = undefined
export let disabled: boolean = false 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 export let onClick: ((event: MouseEvent) => void) | undefined = undefined
const client = getClient() const client = getClient()
@ -58,6 +57,9 @@
doc = object doc = object
} }
$: icon =
doc !== undefined && !hierarchy.isDerived(doc._class, contact.class.Contact) ? classIcon(client, doc._class) : null
$: void updateDocTitle(doc) $: void updateDocTitle(doc)
$: void updateDocTooltip(doc) $: void updateDocTooltip(doc)
$: void updateDocLabel(doc, _class) $: void updateDocLabel(doc, _class)
@ -113,19 +115,9 @@
</script> </script>
{#if displayTitle} {#if displayTitle}
<DocNavLink <span data-type={'reference'} data-id={doc?._id} data-objectclass={doc?._class} data-label={displayTitle}>
object={doc} <DocNavLink object={doc} component={docComponent} {disabled} inlineReference {onClick}>
component={docComponent} {#if icon}<Icon {icon} size="small" />{' '}{:else}@{/if}{displayTitle}
{disabled} </DocNavLink>
{accent} </span>
{colorInherit}
{noUnderline}
inline
noOverflow
{onClick}
>
<span class="antiMention" class:reference={!disabled} use:tooltip={disabled ? undefined : docTooltip}>
@{displayTitle}
</span>
</DocNavLink>
{/if} {/if}

View File

@ -287,7 +287,7 @@ export class DocumentContentPage extends CommonPage {
} }
async checkReferenceInTheText (label: string): Promise<void> { async checkReferenceInTheText (label: string): Promise<void> {
await expect(this.page.locator('span', { hasText: '@' + label })).toHaveAttribute('data-type', 'reference') await expect(this.page.locator('span.antiMention', { hasText: label })).toHaveAttribute('data-type', 'reference')
} }
async executeMoreAction (action: string): Promise<void> { async executeMoreAction (action: string): Promise<void> {

View File

@ -181,7 +181,7 @@ export class CommonTrackerPage extends CalendarPage {
} }
async openLinkFromActivitiesByText (linkText: string): Promise<void> { async openLinkFromActivitiesByText (linkText: string): Promise<void> {
await this.linkInActivity().filter({ hasText: linkText }).click() await this.linkInActivity().filter({ hasText: linkText }).first().click()
} }
async addCommentWithImage (comment: string, fileName: string): Promise<void> { async addCommentWithImage (comment: string, fileName: string): Promise<void> {

View File

@ -112,12 +112,12 @@ test.describe('Mentions issue tests', () => {
const secondId = await issuesPage.getIssueId(backlinkIssueSecond.title) const secondId = await issuesPage.getIssueId(backlinkIssueSecond.title)
await issuesPage.openIssueByName(backlinkIssueSecond.title) await issuesPage.openIssueByName(backlinkIssueSecond.title)
await issuesDetailsPage.addMentions(defaultId) await issuesDetailsPage.addMentions(defaultId)
await issuesDetailsPage.checkCommentExist(`@${defaultId}`) await issuesDetailsPage.checkCommentExist(defaultId)
await issuesDetailsPage.openLinkFromActivitiesByText(`@${defaultId}`) await issuesDetailsPage.openLinkFromActivitiesByText(defaultId)
await issuesDetailsPage.checkIssue(backlinkIssueDefault) await issuesDetailsPage.checkIssue(backlinkIssueDefault)
await issuesDetailsPage.addMentions(secondId) await issuesDetailsPage.addMentions(secondId)
await issuesDetailsPage.checkCommentExist(`@${secondId}`) await issuesDetailsPage.checkCommentExist(secondId)
await issuesDetailsPage.openLinkFromActivitiesByText(`@${secondId}`) await issuesDetailsPage.openLinkFromActivitiesByText(secondId)
await issuesDetailsPage.checkIssue(backlinkIssueSecond) await issuesDetailsPage.checkIssue(backlinkIssueSecond)
await issuesDetailsPage.clickCloseIssueButton() await issuesDetailsPage.clickCloseIssueButton()
}) })