Skip to content

Commit f5de0ee

Browse files
Add new chat fixes (#8687)
Signed-off-by: Kristina Fefelova <[email protected]>
1 parent 4ca6919 commit f5de0ee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+664
-117
lines changed

packages/presentation/src/communication.ts

+11
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
type RequestEvent,
4848
type ResponseEvent,
4949
type UpdateNotificationContextEvent,
50+
type AddCollaboratorsEvent,
5051
MessageRequestEventType,
5152
NotificationRequestEventType
5253
} from '@hcengineering/communication-sdk-types'
@@ -181,6 +182,16 @@ class Client {
181182
await this.sendEvent(event)
182183
}
183184

185+
async addCollaborators (card: CardID, cardType: CardType, collaborators: AccountID[]): Promise<void> {
186+
const event: AddCollaboratorsEvent = {
187+
type: NotificationRequestEventType.AddCollaborators,
188+
card,
189+
cardType,
190+
collaborators
191+
}
192+
await this.sendEvent(event)
193+
}
194+
184195
async createFile (
185196
card: CardID,
186197
message: MessageID,

packages/theme/styles/_next-colors.scss

+7
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@
107107
//MessageInput
108108
--next-message-input-color-background: var(--color-global-dark-gray);
109109
--next-message-input-color-stroke: var(--color-global-charcoal-black);
110+
111+
//Message
112+
--next-message-hover-color-background: var(--color-huly-off-white-5)
110113
}
111114

112115

@@ -165,4 +168,8 @@
165168
//MessageInput
166169
--next-message-input-color-background: var(--color-global-light-gray);
167170
--next-message-input-color-stroke: var(--color-global-white-100);
171+
172+
173+
//Message
174+
--next-message-hover-color-background: var(--color-huly-dark-grey-5)
168175
}

packages/ui-next/lang/cs.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} v {time}",
2727
"YearAt": "{year} v {time}",
2828
"Yesterday": "Včera",
29-
"YesterdayAt": "Včera v {time}"
29+
"YesterdayAt": "Včera v {time}",
30+
"AndMore": "a {count} dalších",
31+
"IsTyping": "{count, plural, =1 {píše} other {píší}}"
3032
}
3133
}

packages/ui-next/lang/de.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} um {time}",
2727
"YearAt": "{year} um {time}",
2828
"Yesterday": "Gestern",
29-
"YesterdayAt": "Gestern um {time}"
29+
"YesterdayAt": "Gestern um {time}",
30+
"AndMore": "und {count} weitere",
31+
"IsTyping": "{count, plural, =1 {schreibt} other {schreiben}}"
3032
}
3133
}

packages/ui-next/lang/en.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} at {time}",
2727
"YearAt": "{year} at {time}",
2828
"Yesterday": "Yesterday",
29-
"YesterdayAt": "Yesterday at {time}"
29+
"YesterdayAt": "Yesterday at {time}",
30+
"AndMore": "and {count} more",
31+
"IsTyping": "{count, plural, =1 {is typing} other {are typing}}..."
3032
}
3133
}

packages/ui-next/lang/es.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} a las {time}",
2727
"YearAt": "{year} a las {time}",
2828
"Yesterday": "Ayer",
29-
"YesterdayAt": "Ayer a las {time}"
29+
"YesterdayAt": "Ayer a las {time}",
30+
"AndMore": "y {count} más",
31+
"IsTyping": "{count, plural, =1 {está escribiendo} other {están escribiendo}}"
3032
}
3133
}

packages/ui-next/lang/fr.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} à {time}",
2727
"YearAt": "{year} à {time}",
2828
"Yesterday": "Hier",
29-
"YesterdayAt": "Hier à {time}"
29+
"YesterdayAt": "Hier à {time}",
30+
"AndMore": "et {count} autres",
31+
"IsTyping": "{count, plural, =1 {est en train d'écrire} other {écrivent}}"
3032
}
3133
}

packages/ui-next/lang/it.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} alle {time}",
2727
"YearAt": "{year} alle {time}",
2828
"Yesterday": "Ieri",
29-
"YesterdayAt": "Ieri alle {time}"
29+
"YesterdayAt": "Ieri alle {time}",
30+
"AndMore": "e {count} altri",
31+
"IsTyping": "{count, plural, =1 {sta scrivendo} other {stanno scrivendo}}"
3032
}
3133
}

packages/ui-next/lang/ja.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday}・{time}",
2727
"YearAt": "{year}・{time}",
2828
"Yesterday": "昨日",
29-
"YesterdayAt": "昨日・{time}"
29+
"YesterdayAt": "昨日・{time}",
30+
"AndMore": "および{count}件の他",
31+
"IsTyping": "{count, plural, =1 {入力中} other {入力中}}"
3032
}
3133
}

packages/ui-next/lang/pt.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} às {time}",
2727
"YearAt": "{year} às {time}",
2828
"Yesterday": "Ontem",
29-
"YesterdayAt": "Ontem às {time}"
29+
"YesterdayAt": "Ontem às {time}",
30+
"AndMore": "e mais {count}",
31+
"IsTyping": "{count, plural, =1 {está digitando} other {estão digitando}}"
3032
}
3133
}

packages/ui-next/lang/ru.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
"WeekdayAt": "{weekday} в {time}",
2626
"YearAt": "{year} в {time}",
2727
"Yesterday": "Вчера",
28-
"YesterdayAt": "Вчера в {time}"
28+
"YesterdayAt": "Вчера в {time}",
29+
"AndMore": "и еще {count}",
30+
"IsTyping": "{count, plural, =1 {печатает} other {печатают}}..."
2931
}
3032
}

packages/ui-next/lang/zh.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"WeekdayAt": "{weekday} 于 {time}",
2727
"YearAt": "{year} 于 {time}",
2828
"Yesterday": "昨天",
29-
"YesterdayAt": "昨天 于 {time}"
29+
"YesterdayAt": "昨天 于 {time}",
30+
"AndMore": "和其他 {count} 条",
31+
"IsTyping": "{count, plural, =1 {正在输入} other {正在输入}}"
3032
}
3133
}

packages/ui-next/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
"@hcengineering/ui": "^0.6.15",
5757
"@hcengineering/view": "^0.6.13",
5858
"@hcengineering/view-resources": "^0.6.0",
59+
"@hcengineering/presence": "^0.6.0",
60+
"@hcengineering/presence-resources": "^0.6.0",
5961
"@tiptap/core": "^2.11.7",
6062
"@tiptap/pm": "^2.11.7",
6163
"fast-equals": "^5.2.2",

packages/ui-next/src/components/DateSeparator.svelte

+17-9
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,20 @@
4444
</script>
4545

4646
<div class="date-separator" class:sticky>
47-
<div class="date-separator__date">
48-
{#await formatDate(date, $themeStore.language) then date}
49-
{date}
50-
{/await}
47+
<div class="date-separator__content">
48+
<div class="date-separator__date">
49+
{#await formatDate(date, $themeStore.language) then date}
50+
{date}
51+
{/await}
52+
</div>
5153
</div>
5254
</div>
5355

5456
<style lang="scss">
5557
.date-separator {
5658
display: flex;
5759
width: 100%;
58-
padding: 0.375rem 0;
59-
align-items: center;
60-
gap: 0.375rem;
61-
border-bottom: 1px solid var(--next-divider-color);
60+
padding: 0 2rem;
6261
background: var(--next-panel-color-background);
6362
6463
&.sticky {
@@ -68,10 +67,19 @@
6867
}
6968
}
7069
70+
.date-separator__content {
71+
display: flex;
72+
width: 100%;
73+
align-items: center;
74+
border-bottom: 1px solid var(--next-divider-color);
75+
gap: 0.375rem;
76+
padding: 0.375rem 0;
77+
}
78+
7179
.date-separator__date {
7280
color: var(--next-text-color-secondary);
7381
font-size: 0.813rem;
74-
font-weight: 500;
82+
font-weight: 400;
7583
padding: 0 1rem;
7684
}
7785
</style>

packages/ui-next/src/components/ReactionPresenter.svelte

+12-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
-->
1515

1616
<script lang="ts">
17+
import { tooltip } from '@hcengineering/ui'
18+
import { PersonId } from '@hcengineering/core'
19+
20+
import ReactionsTooltip from './ReactionsTooltip.svelte'
1721
import Icon from './Icon.svelte'
1822
import { IconSize, IconComponent } from '../types'
1923
@@ -23,13 +27,18 @@
2327
export let count: number | undefined = undefined
2428
export let selected: boolean = false
2529
export let active: boolean = false
30+
export let socialIds: PersonId[] = []
2631
</script>
2732

28-
<!--TODO: add users tooltip-->
29-
3033
<!-- svelte-ignore a11y-click-events-have-key-events -->
3134
<!-- svelte-ignore a11y-no-static-element-interactions -->
32-
<div class="reaction" class:selected class:active on:click>
35+
<div
36+
class="reaction"
37+
class:selected
38+
class:active
39+
on:click
40+
use:tooltip={{ component: ReactionsTooltip, props: { socialIds } }}
41+
>
3342
<div class="reaction__emoji" class:foreground={icon != null}>
3443
{#if icon}
3544
<Icon {icon} size={iconSize} />

packages/ui-next/src/components/ReactionsList.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<ReactionPresenter
5959
{emoji}
6060
selected={reactions.some((it) => me.socialIds.includes(it.creator))}
61+
socialIds={reactions.map((it) => it.creator)}
6162
count={reactions.length}
6263
on:click={() => dispatch('click', emoji)}
6364
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!-- Copyright © 2025 Hardcore Engineering Inc. -->
2+
<!-- -->
3+
<!-- Licensed under the Eclipse Public License, Version 2.0 (the "License"); -->
4+
<!-- you may not use this file except in compliance with the License. You may -->
5+
<!-- obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 -->
6+
<!-- -->
7+
<!-- Unless required by applicable law or agreed to in writing, software -->
8+
<!-- distributed under the License is distributed on an "AS IS" BASIS, -->
9+
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -->
10+
<!-- -->
11+
<!-- See the License for the specific language governing permissions and -->
12+
<!-- limitations under the License. -->
13+
14+
<script lang="ts">
15+
import { PersonId } from '@hcengineering/core'
16+
import { ObjectPresenter } from '@hcengineering/view-resources'
17+
import contact from '@hcengineering/contact'
18+
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
19+
20+
export let socialIds: PersonId[] = []
21+
22+
$: persons = new Set(socialIds.map((user) => $personRefByPersonIdStore.get(user)))
23+
</script>
24+
25+
<div class="m-2 flex-col flex-gap-2">
26+
{#each persons as person (person)}
27+
<ObjectPresenter objectId={person} _class={contact.class.Person} disabled />
28+
{/each}
29+
</div>

packages/ui-next/src/components/TextInput.svelte

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { handler, registerFocus } from '@hcengineering/ui'
2121
import { FocusPosition } from '@tiptap/core'
2222
import { createEventDispatcher } from 'svelte'
23+
import { EditorView } from '@tiptap/pm/view'
2324
import {
2425
EmojiExtension,
2526
IsEmptyContentExtension,
@@ -45,6 +46,7 @@
4546
export let boundary: HTMLElement | undefined = undefined
4647
export let autofocus: FocusPosition = false
4748
export let onCancel: (() => void) | undefined = undefined
49+
export let onPaste: ((view: EditorView, event: ClipboardEvent) => boolean) | undefined = undefined
4850
4951
const dispatch = createEventDispatcher()
5052
@@ -148,9 +150,11 @@
148150
{boundary}
149151
canEmbedFiles={false}
150152
canEmbedImages={false}
153+
dropcursor={false}
151154
editorAttributes={{ class: 'next-text-editor-view' }}
152155
{placeholder}
153156
{placeholderParams}
157+
{onPaste}
154158
on:content={(ev) => {
155159
if (canSubmit(loading, content, hasNonTextContent)) {
156160
dispatch('submit', ev.detail)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!--
2+
// Copyright © 2025 Hardcore Engineering Inc.
3+
//
4+
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License. You may
6+
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
//
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
-->
15+
<script lang="ts">
16+
import { onMount } from 'svelte'
17+
import { getName, Person, getCurrentEmployee } from '@hcengineering/contact'
18+
import { personByIdStore } from '@hcengineering/contact-resources'
19+
import { IdMap, notEmpty } from '@hcengineering/core'
20+
import { getClient } from '@hcengineering/presentation'
21+
import { Label } from '@hcengineering/ui'
22+
import { presenceByObjectId } from '@hcengineering/presence-resources'
23+
import { CardID } from '@hcengineering/communication-types'
24+
25+
import uiNext from '../plugin'
26+
import { type PresenceTyping } from '../types'
27+
28+
export let cardId: CardID
29+
const typingDelay = 2000
30+
const maxTypingPersons = 3
31+
const me = getCurrentEmployee()
32+
const hierarchy = getClient().getHierarchy()
33+
34+
let typingPersonsLabel: string = ''
35+
let typingPersonsCount = 0
36+
let moreCount: number = 0
37+
38+
let typing: PresenceTyping[] = []
39+
$: presence = $presenceByObjectId.get(cardId) ?? []
40+
$: typing = presence.map((p) => p.presence.typing).filter(notEmpty)
41+
42+
$: updateTypingPersons($personByIdStore, typing)
43+
44+
function updateTypingPersons (personById: IdMap<Person>, typingInfo: PresenceTyping[]): void {
45+
const now = Date.now()
46+
const personIds = new Set(
47+
typingInfo.filter((info) => info.person !== me && now - info.lastTyping < typingDelay).map((info) => info.person)
48+
)
49+
const names = Array.from(personIds)
50+
.map((personId) => personById.get(personId))
51+
.filter((person): person is Person => person !== undefined)
52+
.map((person) => getName(hierarchy, person))
53+
.sort((name1, name2) => name1.localeCompare(name2))
54+
55+
typingPersonsCount = names.length
56+
typingPersonsLabel = names.slice(0, maxTypingPersons).join(', ')
57+
moreCount = Math.max(names.length - maxTypingPersons, 0)
58+
}
59+
60+
onMount(() => {
61+
const interval = setInterval(() => {
62+
updateTypingPersons($personByIdStore, typing)
63+
}, typingDelay)
64+
return () => {
65+
clearInterval(interval)
66+
}
67+
})
68+
</script>
69+
70+
<span class="root h-4 mt-1 mb-1 ml-0-5 overflow-label">
71+
{#if typingPersonsLabel !== ''}
72+
<span class="fs-bold">
73+
{typingPersonsLabel}
74+
</span>
75+
{#if moreCount > 0}
76+
<span class="ml-1"><Label label={uiNext.string.AndMore} params={{ count: moreCount }} /></span>
77+
{/if}
78+
<span class="ml-1"><Label label={uiNext.string.IsTyping} params={{ count: typingPersonsCount }} /></span>
79+
{/if}
80+
</span>
81+
82+
<style>
83+
.root {
84+
display: flex;
85+
align-items: center;
86+
font-size: 0.75rem;
87+
}
88+
</style>

0 commit comments

Comments
 (0)