From 161905dc0f2f3e7f41d3ea62c67eef31f30c7cc0 Mon Sep 17 00:00:00 2001 From: Kristina Date: Wed, 25 Jun 2025 11:06:01 +0400 Subject: [PATCH] Add more chat/cards ui fixes (#9358) Signed-off-by: Kristina Fefelova --- models/card/src/index.ts | 6 +- models/card/src/plugin.ts | 5 +- models/communication/package.json | 3 +- models/communication/src/actions.ts | 144 ++++++++++ models/communication/src/index.ts | 33 +-- models/communication/src/types.ts | 37 +++ packages/ui/src/components/Modal.svelte | 1 + .../src/components/CardTagColored.svelte | 6 +- .../src/components/CardTagsColored.svelte | 1 - .../src/components/Childs.svelte | 4 +- .../components/EditCardTableOfContents.svelte | 24 +- .../NavigatorCardsSection.svelte | 30 +- .../sections/AttachmentsSection.svelte | 2 +- .../sections/ChildrenSection.svelte | 2 +- .../sections/PropertiesSection.svelte | 2 +- .../sections/RelationsSection.svelte | 2 +- plugins/card-resources/src/index.ts | 6 +- plugins/card-resources/src/types.ts | 1 + plugins/card-resources/src/utils.ts | 25 +- plugins/card/src/index.ts | 1 + .../src/components/ChatNavigation.svelte | 6 +- plugins/communication-assets/assets/icons.svg | 18 +- plugins/communication-assets/lang/cs.json | 12 +- plugins/communication-assets/lang/de.json | 12 +- plugins/communication-assets/lang/en.json | 12 +- plugins/communication-assets/lang/es.json | 12 +- plugins/communication-assets/lang/fr.json | 12 +- plugins/communication-assets/lang/it.json | 12 +- plugins/communication-assets/lang/ja.json | 12 +- plugins/communication-assets/lang/pt.json | 12 +- plugins/communication-assets/lang/ru.json | 12 +- plugins/communication-assets/lang/zh.json | 12 +- plugins/communication-assets/src/index.ts | 3 +- plugins/communication-resources/package.json | 2 + .../communication-resources/src/actions.ts | 256 +++++++++++++++++ .../CreateCardFromMessagePopup.svelte | 147 ++++++++++ .../src/components/DateSeparator.svelte | 6 +- .../src/components/MessagesList.svelte | 4 +- .../message/MessageActionsPanel.svelte | 136 ++++----- .../src/components/message/MessageBody.svelte | 38 ++- .../message/MessageContentViewer.svelte | 34 ++- .../components/message/MessageFooter.svelte | 9 +- .../components/message/MessageInput.svelte | 2 +- .../message/MessagePresenter.svelte | 186 +++++-------- .../components/message/MessageReplies.svelte | 261 ------------------ .../message/MessagesGroupPresenter.svelte | 2 +- .../src/components/thread/Thread.svelte | 101 +++++++ .../thread/ThreadCollaborators.svelte | 71 +++++ .../components/thread/ThreadLastReply.svelte | 45 +++ .../thread/ThreadRepliesCount.svelte | 34 +++ .../src/components/thread/ThreadTags.svelte | 21 ++ .../src/components/thread/ThreadTitle.svelte | 32 +++ plugins/communication-resources/src/index.ts | 32 ++- plugins/communication-resources/src/plugin.ts | 46 ++- plugins/communication-resources/src/stores.ts | 36 ++- plugins/communication-resources/src/utils.ts | 87 +----- plugins/communication/package.json | 1 + plugins/communication/src/index.ts | 10 +- plugins/communication/src/types.ts | 27 ++ .../components/ProcessesCardSection.svelte | 2 +- 60 files changed, 1464 insertions(+), 646 deletions(-) create mode 100644 models/communication/src/actions.ts create mode 100644 models/communication/src/types.ts create mode 100644 plugins/communication-resources/src/actions.ts create mode 100644 plugins/communication-resources/src/components/CreateCardFromMessagePopup.svelte delete mode 100644 plugins/communication-resources/src/components/message/MessageReplies.svelte create mode 100644 plugins/communication-resources/src/components/thread/Thread.svelte create mode 100644 plugins/communication-resources/src/components/thread/ThreadCollaborators.svelte create mode 100644 plugins/communication-resources/src/components/thread/ThreadLastReply.svelte create mode 100644 plugins/communication-resources/src/components/thread/ThreadRepliesCount.svelte create mode 100644 plugins/communication-resources/src/components/thread/ThreadTags.svelte create mode 100644 plugins/communication-resources/src/components/thread/ThreadTitle.svelte diff --git a/models/card/src/index.ts b/models/card/src/index.ts index 39c72ece5a..289a65710d 100644 --- a/models/card/src/index.ts +++ b/models/card/src/index.ts @@ -59,7 +59,7 @@ import presentation from '@hcengineering/model-presentation' import setting from '@hcengineering/model-setting' import view, { createAction, type Viewlet } from '@hcengineering/model-view' import workbench, { WidgetType } from '@hcengineering/model-workbench' -import { type Asset, getEmbeddedLabel, type IntlString } from '@hcengineering/platform' +import { type Asset, getEmbeddedLabel, type IntlString, type Resource } from '@hcengineering/platform' import time, { type ToDo } from '@hcengineering/time' import { type AnyComponent } from '@hcengineering/ui/src/types' import { type BuildModelKey } from '@hcengineering/view' @@ -132,6 +132,7 @@ export class TCardSection extends TDoc implements CardSection { component!: AnyComponent order!: number navigation!: CardNavigation[] + checkVisibility?: Resource<(doc: Card) => Promise> } @Mixin(card.mixin.CardViewDefaults, card.class.MasterTag) @@ -902,7 +903,8 @@ function defineTabs (builder: Builder): void { label: core.string.Relations, component: card.sectionComponent.RelationsSection, order: 500, - navigation: [] + navigation: [], + checkVisibility: card.function.CheckRelationsSectionVisibility }, card.section.Relations ) diff --git a/models/card/src/plugin.ts b/models/card/src/plugin.ts index 02aaa141ab..4ad6d6420a 100644 --- a/models/card/src/plugin.ts +++ b/models/card/src/plugin.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import { cardId } from '@hcengineering/card' +import { type Card, cardId } from '@hcengineering/card' import card from '@hcengineering/card-resources/src/plugin' import type { Client, Doc, Ref } from '@hcengineering/core' import {} from '@hcengineering/core' @@ -54,7 +54,8 @@ export default mergeIds(cardId, card, { CardIdProvider: '' as Resource<(client: Client, ref: Ref, doc?: Doc) => Promise>, GetCardLink: '' as Resource<(doc: Doc, props: Record) => Promise>, CardCustomLinkMatch: '' as Resource<(doc: Doc) => boolean>, - CardCustomLinkEncode: '' as Resource<(doc: Doc) => Location> + CardCustomLinkEncode: '' as Resource<(doc: Doc) => Location>, + CheckRelationsSectionVisibility: '' as Resource<(doc: Card) => Promise> }, label: { Subscribed: '' as Ref, diff --git a/models/communication/package.json b/models/communication/package.json index 6be655a143..2c46d85dec 100644 --- a/models/communication/package.json +++ b/models/communication/package.json @@ -32,11 +32,12 @@ "@hcengineering/card": "^0.6.0", "@hcengineering/communication": "^0.6.0", "@hcengineering/communication-resources": "^0.6.0", - "@hcengineering/model-card": "^0.6.0", "@hcengineering/contact": "^0.6.24", "@hcengineering/core": "^0.6.32", "@hcengineering/model": "^0.6.11", + "@hcengineering/model-card": "^0.6.0", "@hcengineering/model-core": "^0.6.0", + "@hcengineering/model-emoji": "^0.6.0", "@hcengineering/model-view": "^0.6.0", "@hcengineering/platform": "^0.6.11", "@hcengineering/ui": "^0.6.15" diff --git a/models/communication/src/actions.ts b/models/communication/src/actions.ts new file mode 100644 index 0000000000..53ea42dd85 --- /dev/null +++ b/models/communication/src/actions.ts @@ -0,0 +1,144 @@ +// Copyright © 2025 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. + +import { type Builder } from '@hcengineering/model' +import view, { createAction } from '@hcengineering/model-view' +import card from '@hcengineering/model-card' +import core from '@hcengineering/model-core' +import emoji from '@hcengineering/model-emoji' + +import communication from './plugin' + +export function buildMessageActions (builder: Builder): void { + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.AddReaction, + icon: emoji.icon.Emoji, + action: communication.messageActionImpl.AddReaction, + order: 100 + }, + communication.messageAction.AddReaction + ) + + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.ReplyInThread, + icon: communication.icon.MessageMultiple, + action: communication.messageActionImpl.ReplyInThread, + visibilityTester: communication.function.CanReplyInThread, + order: 200 + }, + communication.messageAction.ReplyInThread + ) + + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.CreateCard, + icon: card.icon.Card, + action: communication.messageActionImpl.CreateCard, + visibilityTester: communication.function.CanCreateCard, + order: 300 + }, + communication.messageAction.CreateCard + ) + + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.TranslateMessage, + icon: view.icon.Translate, + action: communication.messageActionImpl.TranslateMessage, + visibilityTester: communication.function.CanTranslateMessage, + order: 400 + }, + communication.messageAction.TranslateMessage + ) + + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.ShowOriginalMessage, + icon: view.icon.Undo, + action: communication.messageActionImpl.ShowOriginalMessage, + visibilityTester: communication.function.CanShowOriginalMessage, + order: 400 + }, + communication.messageAction.ShowOriginalMessage + ) + + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.EditMessage, + icon: view.icon.Edit, + action: communication.messageActionImpl.EditMessage, + order: 500, + visibilityTester: communication.function.CanEditMessage, + menu: true + }, + communication.messageAction.EditMessage + ) + + builder.createDoc( + communication.class.MessageAction, + core.space.Model, + { + label: communication.string.RemoveMessage, + icon: view.icon.Delete, + action: communication.messageActionImpl.RemoveMessage, + order: 9999, + visibilityTester: communication.function.CanRemoveMessage, + menu: true + }, + communication.messageAction.RemoveMessage + ) +} + +export function buildCardActions (builder: Builder): void { + createAction(builder, { + action: communication.action.Subscribe, + label: communication.string.Subscribe, + icon: communication.icon.Bell, + visibilityTester: communication.function.CanSubscribe, + input: 'focus', + category: card.category.Card, + target: card.class.Card, + context: { + mode: ['context'], + group: 'associate' + } + }) + + createAction(builder, { + action: communication.action.Unsubscribe, + label: communication.string.Unsubscribe, + icon: communication.icon.BellCrossed, + visibilityTester: communication.function.CanUnsubscribe, + input: 'focus', + category: card.category.Card, + target: card.class.Card, + context: { + mode: ['context'], + group: 'associate' + } + }) +} diff --git a/models/communication/src/index.ts b/models/communication/src/index.ts index 8dfdc522f1..1d80155ce8 100644 --- a/models/communication/src/index.ts +++ b/models/communication/src/index.ts @@ -14,42 +14,19 @@ import { type Builder } from '@hcengineering/model' import core from '@hcengineering/core' import card from '@hcengineering/model-card' -import { createAction } from '@hcengineering/model-view' import { MessagesNavigationAnchors } from '@hcengineering/communication' import communication from './plugin' +import { buildTypes } from './types' +import { buildCardActions, buildMessageActions } from './actions' export { communicationId } from '@hcengineering/communication' export * from './migration' export function createModel (builder: Builder): void { - createAction(builder, { - action: communication.action.Subscribe, - label: communication.string.Subscribe, - icon: communication.icon.Bell, - visibilityTester: communication.function.CanSubscribe, - input: 'focus', - category: card.category.Card, - target: card.class.Card, - context: { - mode: ['context'], - group: 'associate' - } - }) - - createAction(builder, { - action: communication.action.Unsubscribe, - label: communication.string.Unsubscribe, - icon: communication.icon.BellCrossed, - visibilityTester: communication.function.CanUnsubscribe, - input: 'focus', - category: card.category.Card, - target: card.class.Card, - context: { - mode: ['context'], - group: 'associate' - } - }) + buildTypes(builder) + buildMessageActions(builder) + buildCardActions(builder) builder.createDoc( card.class.CardSection, diff --git a/models/communication/src/types.ts b/models/communication/src/types.ts new file mode 100644 index 0000000000..c5ffc40e0a --- /dev/null +++ b/models/communication/src/types.ts @@ -0,0 +1,37 @@ +// Copyright © 2025 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. + +import { type Builder, Model } from '@hcengineering/model' +import core, { TDoc } from '@hcengineering/model-core' +import { DOMAIN_MODEL } from '@hcengineering/core' +import { type Asset, type IntlString } from '@hcengineering/platform' +import communication, { + type MessageAction, + type MessageActionFunctionResource, + type MessageActionVisibilityTesterResource +} from '@hcengineering/communication' + +@Model(communication.class.MessageAction, core.class.Doc, DOMAIN_MODEL) +class TMessageAction extends TDoc implements MessageAction { + label!: IntlString + icon!: Asset + action!: MessageActionFunctionResource + order!: number + + visibilityTester?: MessageActionVisibilityTesterResource + menu?: boolean +} + +export function buildTypes (builder: Builder): void { + builder.createModel(TMessageAction) +} diff --git a/packages/ui/src/components/Modal.svelte b/packages/ui/src/components/Modal.svelte index 886e1fac82..d965bc7fc7 100644 --- a/packages/ui/src/components/Modal.svelte +++ b/packages/ui/src/components/Modal.svelte @@ -124,6 +124,7 @@ {#if $$slots.buttons} {/if} + {/if} diff --git a/plugins/card-resources/src/components/CardTagColored.svelte b/plugins/card-resources/src/components/CardTagColored.svelte index f616271244..ec90690d32 100644 --- a/plugins/card-resources/src/components/CardTagColored.svelte +++ b/plugins/card-resources/src/components/CardTagColored.svelte @@ -31,10 +31,6 @@ const dispatch = createEventDispatcher() - function getColorDefinition (color: number | undefined): ColorDefinition { - return getPlatformColorDef(color ?? 0, $themeStore.dark) - } - function getTagStyle (color: ColorDefinition): string { return ` background: ${color.color + '33'}; @@ -48,7 +44,7 @@
{ - if (evt.detail.length > 0) { - dispatch('loaded') - } + dispatch('loaded') listProvider.update(evt.detail) }} /> diff --git a/plugins/card-resources/src/components/EditCardTableOfContents.svelte b/plugins/card-resources/src/components/EditCardTableOfContents.svelte index d54e7b1ee5..17736b1575 100644 --- a/plugins/card-resources/src/components/EditCardTableOfContents.svelte +++ b/plugins/card-resources/src/components/EditCardTableOfContents.svelte @@ -24,6 +24,7 @@ import { Heading } from '@hcengineering/text-editor' import { CardSectionAction } from '../types' + import { getResource } from '@hcengineering/platform' export let doc: Card export let context: NotificationContext | undefined = undefined @@ -33,7 +34,8 @@ const client = getClient() - const sections: CardSection[] = client + let sections: CardSection[] = [] + const _sections: CardSection[] = client .getModel() .findAllSync(card.class.CardSection, {}) .sort((a, b) => a.order - b.order) @@ -188,6 +190,24 @@ } } + async function filterSections (s: CardSection[], doc: Card): Promise { + const newSections: CardSection[] = [] + for (const section of s) { + if (section.checkVisibility !== undefined) { + const isVisibleFn = await getResource(section.checkVisibility) + const isVisible = await isVisibleFn(doc) + if (isVisible) { + newSections.push(section) + } + } else { + newSections.push(section) + } + } + + sections = newSections + } + + $: void filterSections(_sections, doc) $: updateToc(sections, subTocBySection) $: showOverlay = !isScrollInitialized || Object.values(sectionOverlays).some((it) => it) @@ -273,7 +293,7 @@ //inset: 0; z-index: 1; top: 0; - right: 0.75rem; + right: 0.25rem; width: 2rem; height: fit-content; diff --git a/plugins/card-resources/src/components/navigator-next/NavigatorCardsSection.svelte b/plugins/card-resources/src/components/navigator-next/NavigatorCardsSection.svelte index af04415177..22fd26674d 100644 --- a/plugins/card-resources/src/components/navigator-next/NavigatorCardsSection.svelte +++ b/plugins/card-resources/src/components/navigator-next/NavigatorCardsSection.svelte @@ -15,7 +15,7 @@ + + dispatch('close')} + on:close +> +
+ +
+ +
+
+ + { + selectedType = e.detail + }} + /> +
+
+ +
+ + {#if _message.thread != null} + + {/if} + + + + diff --git a/plugins/communication-resources/src/components/DateSeparator.svelte b/plugins/communication-resources/src/components/DateSeparator.svelte index 06d4f41664..62c9e04c67 100644 --- a/plugins/communication-resources/src/components/DateSeparator.svelte +++ b/plugins/communication-resources/src/components/DateSeparator.svelte @@ -55,7 +55,7 @@ .date-separator { display: flex; width: 100%; - padding: 0 2rem; + padding: 0 1rem; font-size: 0.75rem; height: 2.25rem; position: relative; @@ -84,9 +84,9 @@ flex: 1; height: 1px; min-height: 1px; - width: calc(100% - 4rem); + width: calc(100% - 2rem); background: var(--highlight-select-border); - margin: 0 2rem; + margin: 0 1rem; margin-top: -2rem; } diff --git a/plugins/communication-resources/src/components/MessagesList.svelte b/plugins/communication-resources/src/components/MessagesList.svelte index 761aec7a4a..ed42686c68 100644 --- a/plugins/communication-resources/src/components/MessagesList.svelte +++ b/plugins/communication-resources/src/components/MessagesList.svelte @@ -82,7 +82,9 @@ $: query.query(queryDef, (res: Window) => { window = res - messages = queryDef.order === SortingOrder.Ascending ? res.getResult() : res.getResult().reverse() + messages = (queryDef.order === SortingOrder.Ascending ? res.getResult() : res.getResult().reverse()).filter( + (it) => !it.removed || it.thread != null + ) if (messages.length < limit && res.hasNextPage()) { void window.loadNextPage() diff --git a/plugins/communication-resources/src/components/message/MessageActionsPanel.svelte b/plugins/communication-resources/src/components/message/MessageActionsPanel.svelte index 82ee5a7df2..695ea3d7d8 100644 --- a/plugins/communication-resources/src/components/message/MessageActionsPanel.svelte +++ b/plugins/communication-resources/src/components/message/MessageActionsPanel.svelte @@ -14,110 +14,70 @@ -->
- {#each actions as action (action.id)} + {#each inlineActions as action} + {#if action.icon} + handleAction(action, ev)} + /> + {/if} + {/each} + + {#if menuActions.length > 0} - {/each} + {/if}
diff --git a/plugins/communication-resources/src/components/message/MessagesGroupPresenter.svelte b/plugins/communication-resources/src/components/message/MessagesGroupPresenter.svelte index 9ddf41b529..3b8334590b 100644 --- a/plugins/communication-resources/src/components/message/MessagesGroupPresenter.svelte +++ b/plugins/communication-resources/src/components/message/MessagesGroupPresenter.svelte @@ -60,7 +60,7 @@ {#if separatorIndex !== 0 && index === separatorIndex} {/if} - + {/each}
diff --git a/plugins/communication-resources/src/components/thread/Thread.svelte b/plugins/communication-resources/src/components/thread/Thread.svelte new file mode 100644 index 0000000000..20d5aac799 --- /dev/null +++ b/plugins/communication-resources/src/components/thread/Thread.svelte @@ -0,0 +1,101 @@ + + + + +
+ + +
+ + + {#if thread.repliesCount > 0} + + + + + {/if} + + {#if threadCard && clientWidth > 300} + + {/if} + + {#if threadCard && clientWidth > 300} + + {/if} +
+
+ + diff --git a/plugins/communication-resources/src/components/thread/ThreadCollaborators.svelte b/plugins/communication-resources/src/components/thread/ThreadCollaborators.svelte new file mode 100644 index 0000000000..41adce0c85 --- /dev/null +++ b/plugins/communication-resources/src/components/thread/ThreadCollaborators.svelte @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + +{#if persons.length > 0} +
+ {#each persons as person} + + {/each} +
+ + {#if collaborators.length > displayPersonsNumber} + +{collaborators.length - displayPersonsNumber} + {/if} +{/if} + + diff --git a/plugins/communication-resources/src/components/thread/ThreadLastReply.svelte b/plugins/communication-resources/src/components/thread/ThreadLastReply.svelte new file mode 100644 index 0000000000..41215bfe10 --- /dev/null +++ b/plugins/communication-resources/src/components/thread/ThreadLastReply.svelte @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + +{#if time !== undefined} + + +{/if} + + diff --git a/plugins/communication-resources/src/components/thread/ThreadRepliesCount.svelte b/plugins/communication-resources/src/components/thread/ThreadRepliesCount.svelte new file mode 100644 index 0000000000..9ed42e537e --- /dev/null +++ b/plugins/communication-resources/src/components/thread/ThreadRepliesCount.svelte @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + +{#if count > 0} + + +{/if} + + diff --git a/plugins/communication-resources/src/components/thread/ThreadTags.svelte b/plugins/communication-resources/src/components/thread/ThreadTags.svelte new file mode 100644 index 0000000000..aa1cc8fcea --- /dev/null +++ b/plugins/communication-resources/src/components/thread/ThreadTags.svelte @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/plugins/communication-resources/src/components/thread/ThreadTitle.svelte b/plugins/communication-resources/src/components/thread/ThreadTitle.svelte new file mode 100644 index 0000000000..3c9646dcbe --- /dev/null +++ b/plugins/communication-resources/src/components/thread/ThreadTitle.svelte @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + {title} + + + diff --git a/plugins/communication-resources/src/index.ts b/plugins/communication-resources/src/index.ts index 2272096614..355f222b42 100644 --- a/plugins/communication-resources/src/index.ts +++ b/plugins/communication-resources/src/index.ts @@ -17,6 +17,21 @@ import { type Resources } from '@hcengineering/platform' import CardMessagesSection from './components/CardMessagesSection.svelte' import { unsubscribe, subscribe, canSubscribe, canUnsubscribe } from './utils' +import { + addReaction, + canCreateCard, + canEditMessage, + canRemoveMessage, + canReplyInThread, + canShowOriginalMessage, + canTranslateMessage, + createCard, + editMessage, + removeMessage, + replyInThread, + showOriginalMessage, + translateMessage +} from './actions' export { isActivityMessage } from './activity' export * from './stores' @@ -29,12 +44,27 @@ export default async (): Promise => ({ component: { CardMessagesSection }, + messageActionImpl: { + AddReaction: addReaction, + ReplyInThread: replyInThread, + TranslateMessage: translateMessage, + ShowOriginalMessage: showOriginalMessage, + EditMessage: editMessage, + RemoveMessage: removeMessage, + CreateCard: createCard + }, action: { Unsubscribe: unsubscribe, Subscribe: subscribe }, function: { CanSubscribe: canSubscribe, - CanUnsubscribe: canUnsubscribe + CanUnsubscribe: canUnsubscribe, + CanReplyInThread: canReplyInThread, + CanTranslateMessage: canTranslateMessage, + CanShowOriginalMessage: canShowOriginalMessage, + CanEditMessage: canEditMessage, + CanRemoveMessage: canRemoveMessage, + CanCreateCard: canCreateCard } }) diff --git a/plugins/communication-resources/src/plugin.ts b/plugins/communication-resources/src/plugin.ts index 3d735f24e1..482f36e623 100644 --- a/plugins/communication-resources/src/plugin.ts +++ b/plugins/communication-resources/src/plugin.ts @@ -12,8 +12,14 @@ // limitations under the License. import { type IntlString, mergeIds } from '@hcengineering/platform' -import communication, { communicationId } from '@hcengineering/communication' +import communication, { + communicationId, + type MessageAction, + type MessageActionFunctionResource, + type MessageActionVisibilityTesterResource +} from '@hcengineering/communication' import { type AnyComponent } from '@hcengineering/ui' +import { type Ref } from '@hcengineering/core' export default mergeIds(communicationId, communication, { component: { @@ -54,6 +60,42 @@ export default mergeIds(communicationId, communication, { ThreadWasRemoved: '' as IntlString, MessageWasRemoved: '' as IntlString, JoinedThe: '' as IntlString, - LeftThe: '' as IntlString + LeftThe: '' as IntlString, + Translating: '' as IntlString, + ShowOriginal: '' as IntlString, + AddReaction: '' as IntlString, + ReplyInThread: '' as IntlString, + TranslateMessage: '' as IntlString, + ShowOriginalMessage: '' as IntlString, + EditMessage: '' as IntlString, + RemoveMessage: '' as IntlString, + CreateCard: '' as IntlString, + MessageAlreadyHasCardAttached: '' as IntlString + }, + messageActionImpl: { + AddReaction: '' as MessageActionFunctionResource, + ReplyInThread: '' as MessageActionFunctionResource, + TranslateMessage: '' as MessageActionFunctionResource, + ShowOriginalMessage: '' as MessageActionFunctionResource, + EditMessage: '' as MessageActionFunctionResource, + RemoveMessage: '' as MessageActionFunctionResource, + CreateCard: '' as MessageActionFunctionResource + }, + messageAction: { + AddReaction: '' as Ref, + ReplyInThread: '' as Ref, + TranslateMessage: '' as Ref, + ShowOriginalMessage: '' as Ref, + EditMessage: '' as Ref, + RemoveMessage: '' as Ref, + CreateCard: '' as Ref + }, + function: { + CanReplyInThread: '' as MessageActionVisibilityTesterResource, + CanTranslateMessage: '' as MessageActionVisibilityTesterResource, + CanShowOriginalMessage: '' as MessageActionVisibilityTesterResource, + CanEditMessage: '' as MessageActionVisibilityTesterResource, + CanRemoveMessage: '' as MessageActionVisibilityTesterResource, + CanCreateCard: '' as MessageActionVisibilityTesterResource } }) diff --git a/plugins/communication-resources/src/stores.ts b/plugins/communication-resources/src/stores.ts index b9a81119ae..8753904e58 100644 --- a/plugins/communication-resources/src/stores.ts +++ b/plugins/communication-resources/src/stores.ts @@ -11,11 +11,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { writable } from 'svelte/store' +import { get, writable } from 'svelte/store' import { createLabelsQuery, onCommunicationClient } from '@hcengineering/presentation' -import type { Label } from '@hcengineering/communication-types' +import type { Label, Message, MessageID } from '@hcengineering/communication-types' +import type { Markup } from '@hcengineering/core' +import { languageStore } from '@hcengineering/ui' export const labelsStore = writable([]) +export const messageEditingStore = writable(undefined) +export const translateMessagesStore = writable>(new Map()) +export const threadCreateMessageStore = writable(undefined) + +export interface TranslateMessagesStatus { + inProgress: boolean + shown: boolean + result?: Markup +} + +languageStore.subscribe(() => { + translateMessagesStore.set(new Map()) +}) + +export function isMessageTranslated (messageId: MessageID): boolean { + return get(translateMessagesStore).get(messageId)?.result != null +} + +export function isMessageTranslating (messageId: MessageID): boolean { + return get(translateMessagesStore).get(messageId)?.inProgress === true +} + +export function getMessageTranslatedMarkup (messageId: MessageID): Markup | undefined { + return get(translateMessagesStore).get(messageId)?.result +} + +export function isShownTranslatedMessage (messageId: MessageID): boolean { + const result = get(translateMessagesStore).get(messageId) + return result?.shown === true && result?.result != null +} const query = createLabelsQuery(true) diff --git a/plugins/communication-resources/src/utils.ts b/plugins/communication-resources/src/utils.ts index 29655c576e..44efbff72d 100644 --- a/plugins/communication-resources/src/utils.ts +++ b/plugins/communication-resources/src/utils.ts @@ -11,33 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { - canDisplayLinkPreview, - fetchLinkPreviewDetails, - getClient, - getCommunicationClient -} from '@hcengineering/presentation' -import cardPlugin, { type Card } from '@hcengineering/card' -import { - fillDefaults, - generateId, - getCurrentAccount, - type Markup, - type MarkupBlobRef, - type Ref, - SortingOrder -} from '@hcengineering/core' -import { getMetadata, getResource } from '@hcengineering/platform' +import { canDisplayLinkPreview, fetchLinkPreviewDetails, getCommunicationClient } from '@hcengineering/presentation' +import { type Card } from '@hcengineering/card' +import { getCurrentAccount, type Markup } from '@hcengineering/core' +import { getMetadata } from '@hcengineering/platform' import { showPopup } from '@hcengineering/ui' -import { employeeByPersonIdStore } from '@hcengineering/contact-resources' -import { getEmployeeBySocialId } from '@hcengineering/contact' import { type LinkPreviewData, type Message } from '@hcengineering/communication-types' import emoji from '@hcengineering/emoji' import { markdownToMarkup, markupToMarkdown } from '@hcengineering/text-markdown' -import { jsonToMarkup, markupToJSON, markupToText } from '@hcengineering/text' -import { get } from 'svelte/store' -import chat from '@hcengineering/chat' -import { makeRank } from '@hcengineering/rank' +import { jsonToMarkup, markupToJSON } from '@hcengineering/text' import IconAt from './components/icons/At.svelte' @@ -127,65 +109,6 @@ export async function toggleReaction (message: Message, emoji: string): Promise< } } -export async function replyToThread (message: Message, parentCard: Card): Promise { - const client = getClient() - const communicationClient = getCommunicationClient() - const hierarchy = client.getHierarchy() - - const thread = message.thread - if (thread != null) { - const _id = thread.threadId - const card = await client.findOne(cardPlugin.class.Card, { _id: _id as Ref }) - if (card === undefined) return - const r = await getResource(cardPlugin.function.OpenCardInSidebar) - await r(_id, card) - return - } - - const author = - get(employeeByPersonIdStore).get(message.creator) ?? (await getEmployeeBySocialId(client, message.creator)) - const lastOne = await client.findOne(cardPlugin.class.Card, {}, { sort: { rank: SortingOrder.Descending } }) - const title = createThreadTitle(message, parentCard) - const data = fillDefaults( - hierarchy, - { - title, - rank: makeRank(lastOne?.rank, undefined), - content: '' as MarkupBlobRef, - parent: parentCard._id, - blobs: {}, - parentInfo: [ - ...(parentCard.parentInfo ?? []), - { - _id: parentCard._id, - _class: parentCard._class, - title: parentCard.title - } - ] - }, - chat.masterTag.Thread - ) - const apply = client.apply('create thread', undefined, true) - const threadCardID = generateId() - await apply.createDoc(chat.masterTag.Thread, cardPlugin.space.Default, data, threadCardID) - await apply.commit() - await communicationClient.attachThread(parentCard._id, message.id, threadCardID, chat.masterTag.Thread) - if (author?.active === true && author?.personUuid !== undefined) { - await communicationClient.addCollaborators(threadCardID, chat.masterTag.Thread, [author.personUuid]) - } - const threadCard = await client.findOne(cardPlugin.class.Card, { _id: threadCardID }) - if (threadCard === undefined) return - const r = await getResource(cardPlugin.function.OpenCardInSidebar) - await r(threadCard._id, threadCard) -} - -function createThreadTitle (message: Message, parent: Card): string { - const markup = jsonToMarkup(markdownToMarkup(message.content)) - const messageText = markupToText(markup).trim() - - return messageText.length > 0 ? messageText : `Thread from ${parent.title}` -} - export async function loadLinkPreviewData (url: string): Promise { try { const meta = await fetchLinkPreviewDetails(url) diff --git a/plugins/communication/package.json b/plugins/communication/package.json index fcb778e046..c8ac4ace2e 100644 --- a/plugins/communication/package.json +++ b/plugins/communication/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@hcengineering/platform": "^0.6.11", + "@hcengineering/communication-types": "^0.1.0", "@hcengineering/core": "^0.6.32", "@hcengineering/ui": "^0.6.15", "@hcengineering/card": "^0.6.0" diff --git a/plugins/communication/src/index.ts b/plugins/communication/src/index.ts index 99ae135905..45192a8d40 100644 --- a/plugins/communication/src/index.ts +++ b/plugins/communication/src/index.ts @@ -13,7 +13,9 @@ import { Asset, IntlString, Metadata, plugin, Plugin } from '@hcengineering/platform' import { CardSection } from '@hcengineering/card' -import { Ref } from '@hcengineering/core' +import { Class, Ref } from '@hcengineering/core' + +import { MessageAction } from './types' export * from './types' @@ -23,10 +25,14 @@ export * from './types' export const communicationId = 'communication' as Plugin export default plugin(communicationId, { + class: { + MessageAction: '' as Ref> + }, icon: { Bell: '' as Asset, BellCrossed: '' as Asset, - File: '' as Asset + File: '' as Asset, + MessageMultiple: '' as Asset }, metadata: { Enabled: '' as Metadata diff --git a/plugins/communication/src/types.ts b/plugins/communication/src/types.ts index 35b3a3486d..1b8733a17b 100644 --- a/plugins/communication/src/types.ts +++ b/plugins/communication/src/types.ts @@ -11,7 +11,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { Message } from '@hcengineering/communication-types' +import { Doc } from '@hcengineering/core' +import { Asset, IntlString, Resource } from '@hcengineering/platform' +import { Card } from '@hcengineering/card' + export enum MessagesNavigationAnchors { ConversationStart = 'conversationStart', LatestMessages = 'latestMessages' } + +export type MessageActionFunction = ( + message: Message, + card: Card, + evt?: Event, + onOpen?: () => void, + onClose?: () => void +) => Promise +export type MessageActionFunctionResource = Resource + +export type MessageActionVisibilityTester = (message: Message) => boolean +export type MessageActionVisibilityTesterResource = Resource + +export interface MessageAction extends Doc { + label: IntlString + icon: Asset + action: MessageActionFunctionResource + visibilityTester?: MessageActionVisibilityTesterResource + + order: number + menu?: boolean +} diff --git a/plugins/process-resources/src/components/ProcessesCardSection.svelte b/plugins/process-resources/src/components/ProcessesCardSection.svelte index 028b637b30..4fcf02a89d 100644 --- a/plugins/process-resources/src/components/ProcessesCardSection.svelte +++ b/plugins/process-resources/src/components/ProcessesCardSection.svelte @@ -28,7 +28,7 @@ .processes__section { display: flex; flex-direction: column; - padding: 0 2rem; + padding: 0 1rem; width: 100%; }