Skip to content

Commit 136ce0f

Browse files
Show emojis in mentions (#10034)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
1 parent e7ff7c0 commit 136ce0f

File tree

7 files changed

+112
-21
lines changed

7 files changed

+112
-21
lines changed

packages/core/src/classes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export type PropertyType = any
125125
export interface UXObject extends Obj {
126126
label: IntlString
127127
icon?: Asset
128+
color?: number
128129
hidden?: boolean
129130
readonly?: boolean
130131
}

packages/presentation/src/components/IconWithEmoji.svelte

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
2020
export let icon: number | number[] | Ref<Blob>
2121
export let size: IconSize
22+
export let inline: boolean = false
2223
2324
let value: string | undefined = parseIcon(icon)
2425
@@ -39,15 +40,15 @@
3940
$: value = parseIcon(icon)
4041
</script>
4142

42-
<div class="emoji-{size} flex-row-center emoji">
43+
<span class="emoji-{size} flex-row-center emoji" class:inline class:fitSize={inline}>
4344
{#if value !== undefined}
4445
{value}
4546
{:else}
4647
{#await getBlobRef(asRef(icon)) then iconBlob}
4748
<img src={iconBlob.src} srcset={iconBlob.srcset} alt="icon" />
4849
{/await}
4950
{/if}
50-
</div>
51+
</span>
5152

5253
<style lang="scss">
5354
.emoji {
@@ -59,6 +60,15 @@
5960
img {
6061
margin: 0;
6162
}
63+
64+
&.inline {
65+
display: inline;
66+
67+
img {
68+
margin-bottom: -0.125rem;
69+
vertical-align: unset;
70+
}
71+
}
6272
}
6373
.emoji-inline {
6474
width: 1em;
@@ -70,6 +80,13 @@
7080
height: 0.75rem;
7181
font-size: 0.75rem;
7282
}
83+
84+
.emoji-smaller {
85+
width: 0.875rem;
86+
height: 0.875rem;
87+
font-size: 0.875rem;
88+
}
89+
7390
.emoji-small {
7491
width: 1rem;
7592
height: 1rem;
@@ -89,6 +106,7 @@
89106
width: inherit;
90107
height: inherit;
91108
}
109+
.emoji-smaller,
92110
.emoji-x-small,
93111
.emoji-small,
94112
.emoji-medium,

packages/presentation/src/components/markup/ObjectNode.svelte

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import { createQuery, getClient } from '../../utils'
2222
import MessageBox from '../MessageBox.svelte'
2323
import presentation from '../../plugin'
24+
import { IconWithEmoji } from '../../index'
2425
2526
export let _id: Ref<Doc> | undefined = undefined
2627
export let _class: Ref<Class<Doc>> | undefined = undefined
@@ -35,12 +36,14 @@
3536
let broken: boolean = false
3637
const withoutDoc: Ref<Doc>[] = [contact.mention.Here, contact.mention.Everyone]
3738
39+
$: clazz = _class ? hierarchy.findClass(_class) : undefined
40+
3841
$: icon =
3942
_class !== undefined &&
4043
hierarchy.hasClass(_class) &&
4144
!withoutDoc.includes(_id as any) &&
4245
!hierarchy.isDerived(_class, contact.class.Contact)
43-
? hierarchy.getClass(_class).icon
46+
? clazz?.icon
4447
: null
4548
4649
$: if (_class != null && _id != null && hierarchy.hasClass(_class) && !withoutDoc.includes(_id as any)) {
@@ -64,8 +67,12 @@
6467
<!-- svelte-ignore a11y-click-events-have-key-events -->
6568
<!-- svelte-ignore a11y-no-static-element-interactions -->
6669
<span class="antiMention" class:transparent class:broken on:click={onBrokenLinkClick}>
67-
{#if icon}<Icon {icon} size="small" />{' '}{:else}@{/if}{#if _id === contact.mention.Here}<span class="lower"
68-
><Label label={contact.string.Here} /></span
70+
{#if icon}{#if icon === view.ids.IconWithEmoji}<IconWithEmoji
71+
icon={clazz?.color ?? 0}
72+
size={'smaller'}
73+
inline
74+
/>{:else}<Icon {icon} size="small" />{/if}{' '}{:else}@{/if}{#if _id === contact.mention.Here}<span
75+
class="lower"><Label label={contact.string.Here} /></span
6976
>{:else if _id === contact.mention.Everyone}<span class="lower"><Label label={contact.string.Everyone} /></span
7077
>{:else}{title}{/if}
7178
</span>

packages/theme/styles/common.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@
532532

533533
&.transparent {
534534
background-color: transparent;
535-
535+
536536
&:hover {
537537
background-color: transparent;
538538
}

plugins/communication-resources/src/components/message/activity/ActivityObjectValue.svelte

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
-->
1515
<script lang="ts">
1616
import { getClient } from '@hcengineering/presentation'
17-
import { type Card } from '@hcengineering/card'
17+
import cardPlugin, { type Card } from '@hcengineering/card'
1818
import { type ActivityMessage } from '@hcengineering/communication-types'
1919
import view from '@hcengineering/view'
20-
import { DocNavLink } from '@hcengineering/view-resources'
20+
import { DocNavLink, ObjectIcon } from '@hcengineering/view-resources'
2121
import { Icon, Label } from '@hcengineering/ui'
2222
2323
import communication from './../../../plugin'
@@ -34,11 +34,13 @@
3434
</script>
3535

3636
<span class="container flex-gap-1 overflow-label">
37-
{#if clazz.icon}
38-
<span class="icon mr-1">
39-
<Icon icon={clazz.icon} size="small" />
40-
</span>
41-
{/if}
37+
<span class="icon mr-1">
38+
{#if hierarchy.isDerived(card._class, communication.type.Direct)}
39+
<Icon icon={clazz.icon ?? cardPlugin.icon.Card} size="small" />
40+
{:else}
41+
<ObjectIcon value={card} size={'small'} />
42+
{/if}
43+
</span>
4244

4345
{#if action === 'create'}
4446
<Label label={communication.string.New} />

plugins/text-editor-resources/src/components/extension/reference.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ import { SvelteRenderer } from '../node-view'
2121
import { ReferenceNode, type ReferenceNodeProps, type ReferenceOptions } from '@hcengineering/text'
2222
import Suggestion, { type SuggestionKeyDownProps, type SuggestionOptions, type SuggestionProps } from './suggestion'
2323

24-
import { type Class, type Doc, type Ref } from '@hcengineering/core'
24+
import { type Blob, type Class, type Doc, type Ref } from '@hcengineering/core'
2525
import { getMetadata, getResource, translate } from '@hcengineering/platform'
26-
import presentation, { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
26+
import presentation, { createQuery, getBlobRef, getClient, MessageBox } from '@hcengineering/presentation'
2727
import view from '@hcengineering/view'
2828

2929
import contact from '@hcengineering/contact'
30-
import { parseLocation, showPopup, tooltip, type LabelAndProps, type Location } from '@hcengineering/ui'
30+
import { parseLocation, showPopup, tooltip, type LabelAndProps, type Location, fromCodePoint } from '@hcengineering/ui'
3131
import workbench, { type Application } from '@hcengineering/workbench'
3232

3333
export interface ReferenceExtensionOptions extends ReferenceOptions {
@@ -145,7 +145,7 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
145145
titleSpan.innerText = `${iconUrl !== '' ? '' : options.suggestion.char}${trans}`
146146
root.classList.add('lower')
147147
} else {
148-
titleSpan.innerText = `${iconUrl !== '' ? '' : options.suggestion.char}${props.label ?? props.id}`
148+
titleSpan.innerText = `${iconUrl !== '' || emojiCode != null ? '' : options.suggestion.char}${props.label ?? props.id}`
149149
}
150150
if (broken) {
151151
root.classList.add('broken')
@@ -156,10 +156,17 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
156156

157157
const icon =
158158
objectclass !== undefined && !hierarchy.isDerived(objectclass, contact.class.Contact)
159-
? hierarchy.getClass(objectclass).icon
159+
? hierarchy.findClass(objectclass)?.icon
160160
: undefined
161161

162-
const iconUrl = typeof icon === 'string' ? getMetadata(icon) ?? 'https://anticrm.org/logo.svg' : ''
162+
let iconUrl = ''
163+
let emojiCode: number | undefined
164+
165+
if (icon === view.ids.IconWithEmoji) {
166+
emojiCode = hierarchy.findClass(objectclass)?.color ?? 0
167+
} else if (typeof icon === 'string') {
168+
iconUrl = getMetadata(icon) ?? 'https://anticrm.org/logo.svg'
169+
}
163170

164171
if (iconUrl !== '') {
165172
const svg = root.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))
@@ -168,6 +175,10 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
168175
svg.setAttribute('fill', 'currentColor')
169176
const use = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'use'))
170177
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', iconUrl)
178+
} else if (emojiCode !== undefined) {
179+
const emojiEl = createEmojiElement(emojiCode)
180+
root.appendChild(emojiEl)
181+
root.appendChild(document.createTextNode(' '))
171182
}
172183

173184
let tooltipHandle: any
@@ -575,3 +586,49 @@ function makeQuery (obj: Record<string, string | number | boolean | null | undef
575586
})
576587
.join('&')
577588
}
589+
590+
function parseEmoji (icon: number | number[] | Ref<Blob>): string | undefined {
591+
if (typeof icon === 'object' && '__ref' in icon) {
592+
return undefined
593+
}
594+
try {
595+
return Array.isArray(icon) ? fromCodePoint(...icon) : fromCodePoint(icon as number)
596+
} catch (err) {}
597+
return undefined
598+
}
599+
600+
function createEmojiElement (emojiCode: number | number[] | Ref<Blob>): HTMLElement {
601+
const root = document.createElement('span')
602+
Object.assign(root.style, {
603+
display: 'inline',
604+
flexShrink: '0',
605+
color: 'black'
606+
})
607+
608+
const value = parseEmoji(emojiCode as any)
609+
if (value !== undefined) {
610+
root.textContent = value
611+
return root
612+
}
613+
614+
const placeholder = document.createElement('span')
615+
placeholder.textContent = '@'
616+
root.appendChild(placeholder)
617+
618+
void getBlobRef(emojiCode as Ref<Blob>).then((iconBlob) => {
619+
if (!root.isConnected) return
620+
621+
const img = document.createElement('img')
622+
img.alt = 'icon'
623+
img.src = iconBlob.src
624+
if (iconBlob.srcset != null) img.srcset = iconBlob.srcset
625+
img.style.display = 'inline'
626+
img.style.margin = '0'
627+
img.style.maxHeight = '0.875rem'
628+
img.style.marginBottom = '-0.125rem'
629+
img.style.verticalAlign = 'unset'
630+
root.replaceChild(img, placeholder)
631+
})
632+
633+
return root
634+
}

plugins/view-resources/src/components/ObjectMention.svelte

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<script lang="ts">
1616
import { Class, Doc, Ref } from '@hcengineering/core'
1717
import { getResource, translateCB } from '@hcengineering/platform'
18-
import { createQuery, getClient } from '@hcengineering/presentation'
18+
import { createQuery, getClient, IconWithEmoji } from '@hcengineering/presentation'
1919
import { AnyComponent, Icon, LabelAndProps, themeStore, tooltip } from '@hcengineering/ui'
2020
import view from '@hcengineering/view'
2121
@@ -60,6 +60,8 @@
6060
doc = object
6161
}
6262
63+
$: cl = doc?._class ?? _class
64+
$: clazz = cl ? hierarchy.findClass(cl) : undefined
6365
$: icon =
6466
doc !== undefined && !hierarchy.isDerived(doc._class, contact.class.Contact) ? classIcon(client, doc._class) : null
6567
@@ -126,7 +128,11 @@
126128
use:tooltip={docTooltip}
127129
>
128130
<DocNavLink object={doc} component={docComponent} {disabled} inlineReference {onClick} {transparent}>
129-
{#if icon}<Icon {icon} size="small" />{' '}{:else}@{/if}{displayTitle}
131+
{#if icon}{#if icon === view.ids.IconWithEmoji}<IconWithEmoji
132+
icon={clazz?.color ?? 0}
133+
size={'smaller'}
134+
inline
135+
/>{:else}<Icon {icon} size="small" />{/if}{' '}{:else}@{/if}{displayTitle}
130136
</DocNavLink>
131137
</span>
132138
{/if}

0 commit comments

Comments
 (0)